提交 d1031cde 编写于 作者: T Tom Lane

Adjust date/time input parsing code to correctly distinguish the four

SQLSTATE error codes required by SQL99 (invalid format, datetime field
overflow, interval field overflow, invalid time zone displacement value).
Also emit a HINT about DateStyle in cases where it seems appropriate.
Per recent gripes.
上级 37222260
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/date.c,v 1.90 2003/08/08 00:10:31 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/adt/date.c,v 1.91 2003/08/27 23:29:27 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -62,20 +62,19 @@ date_in(PG_FUNCTION_ARGS)
int tzp;
int dtype;
int nf;
int dterr;
char *field[MAXDATEFIELDS];
int ftype[MAXDATEFIELDS];
char lowstr[MAXDATELEN + 1];
if (strlen(str) >= sizeof(lowstr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for date: \"%s\"", str)));
if ((ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf) != 0)
|| (DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp) != 0))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for date: \"%s\"", str)));
dterr = DTERR_BAD_FORMAT;
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tzp);
if (dterr != 0)
DateTimeParseError(dterr, str, "date");
switch (dtype)
{
......@@ -95,9 +94,8 @@ date_in(PG_FUNCTION_ARGS)
break;
default:
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for date: \"%s\"", str)));
DateTimeParseError(DTERR_BAD_FORMAT, str, "date");
break;
}
date = date2j(tm->tm_year, tm->tm_mon, tm->tm_mday) - POSTGRES_EPOCH_JDATE;
......@@ -559,21 +557,20 @@ time_in(PG_FUNCTION_ARGS)
*tm = &tt;
int tz;
int nf;
int dterr;
char lowstr[MAXDATELEN + 1];
char *field[MAXDATEFIELDS];
int dtype;
int ftype[MAXDATEFIELDS];
if (strlen(str) >= sizeof(lowstr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for time: \"%s\"", str)));
if ((ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf) != 0)
|| (DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz) != 0))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for time: \"%s\"", str)));
dterr = DTERR_BAD_FORMAT;
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
if (dterr != 0)
DateTimeParseError(dterr, str, "time");
tm2time(tm, fsec, &result);
AdjustTimeForTypmod(&result, typmod);
......@@ -1424,23 +1421,20 @@ timetz_in(PG_FUNCTION_ARGS)
*tm = &tt;
int tz;
int nf;
int dterr;
char lowstr[MAXDATELEN + 1];
char *field[MAXDATEFIELDS];
int dtype;
int ftype[MAXDATEFIELDS];
if (strlen(str) >= sizeof(lowstr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for time with time zone: \"%s\"",
str)));
if ((ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf) != 0)
|| (DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz) != 0))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for time with time zone: \"%s\"",
str)));
dterr = DTERR_BAD_FORMAT;
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
dterr = DecodeTimeOnly(field, ftype, nf, &dtype, tm, &fsec, &tz);
if (dterr != 0)
DateTimeParseError(dterr, str, "time with time zone");
result = (TimeTzADT *) palloc(sizeof(TimeTzADT));
tm2timetz(tm, fsec, tz, result);
......
此差异已折叠。
......@@ -10,7 +10,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/nabstime.c,v 1.114 2003/08/17 19:58:05 tgl Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/adt/nabstime.c,v 1.115 2003/08/27 23:29:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -364,6 +364,7 @@ abstimein(PG_FUNCTION_ARGS)
int tz = 0;
struct tm date,
*tm = &date;
int dterr;
char *field[MAXDATEFIELDS];
char lowstr[MAXDATELEN + 1];
int dtype;
......@@ -371,15 +372,13 @@ abstimein(PG_FUNCTION_ARGS)
ftype[MAXDATEFIELDS];
if (strlen(str) >= sizeof(lowstr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for abstime: \"%s\"", str)));
if ((ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf) != 0)
|| (DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz) != 0))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for abstime: \"%s\"", str)));
dterr = DTERR_BAD_FORMAT;
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
if (dterr != 0)
DateTimeParseError(dterr, str, "abstime");
switch (dtype)
{
......@@ -768,21 +767,24 @@ reltimein(PG_FUNCTION_ARGS)
*tm = &tt;
fsec_t fsec;
int dtype;
int dterr;
char *field[MAXDATEFIELDS];
int nf,
ftype[MAXDATEFIELDS];
char lowstr[MAXDATELEN + 1];
if (strlen(str) >= sizeof(lowstr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for reltime: \"%s\"", str)));
if ((ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf) != 0)
|| (DecodeInterval(field, ftype, nf, &dtype, tm, &fsec) != 0))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for reltime: \"%s\"", str)));
dterr = DTERR_BAD_FORMAT;
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
dterr = DecodeInterval(field, ftype, nf, &dtype, tm, &fsec);
if (dterr != 0)
{
if (dterr == DTERR_FIELD_OVERFLOW)
dterr = DTERR_INTERVAL_OVERFLOW;
DateTimeParseError(dterr, str, "reltime");
}
switch (dtype)
{
......
......@@ -8,7 +8,7 @@
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/utils/adt/timestamp.c,v 1.93 2003/08/26 21:31:11 momjian Exp $
* $Header: /cvsroot/pgsql/src/backend/utils/adt/timestamp.c,v 1.94 2003/08/27 23:29:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -77,22 +77,19 @@ timestamp_in(PG_FUNCTION_ARGS)
int tz;
int dtype;
int nf;
int dterr;
char *field[MAXDATEFIELDS];
int ftype[MAXDATEFIELDS];
char lowstr[MAXDATELEN + MAXDATEFIELDS];
if (strlen(str) >= sizeof(lowstr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for timestamp: \"%s\"",
str)));
if ((ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf) != 0)
|| (DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz) != 0))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for timestamp: \"%s\"",
str)));
dterr = DTERR_BAD_FORMAT;
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
if (dterr != 0)
DateTimeParseError(dterr, str, "timestamp");
switch (dtype)
{
......@@ -306,22 +303,19 @@ timestamptz_in(PG_FUNCTION_ARGS)
int tz;
int dtype;
int nf;
int dterr;
char *field[MAXDATEFIELDS];
int ftype[MAXDATEFIELDS];
char lowstr[MAXDATELEN + MAXDATEFIELDS];
if (strlen(str) >= sizeof(lowstr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for timestamp with time zone: \"%s\"",
str)));
if ((ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf) != 0)
|| (DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz) != 0))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for timestamp with time zone: \"%s\"",
str)));
dterr = DTERR_BAD_FORMAT;
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
dterr = DecodeDateTime(field, ftype, nf, &dtype, tm, &fsec, &tz);
if (dterr != 0)
DateTimeParseError(dterr, str, "timestamp with time zone");
switch (dtype)
{
......@@ -468,6 +462,7 @@ interval_in(PG_FUNCTION_ARGS)
*tm = &tt;
int dtype;
int nf;
int dterr;
char *field[MAXDATEFIELDS];
int ftype[MAXDATEFIELDS];
char lowstr[MAXDATELEN + MAXDATEFIELDS];
......@@ -481,17 +476,17 @@ interval_in(PG_FUNCTION_ARGS)
fsec = 0;
if (strlen(str) >= sizeof(lowstr))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for interval: \"%s\"",
str)));
if ((ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf) != 0)
|| (DecodeInterval(field, ftype, nf, &dtype, tm, &fsec) != 0))
ereport(ERROR,
(errcode(ERRCODE_INVALID_DATETIME_FORMAT),
errmsg("invalid input syntax for interval: \"%s\"",
str)));
dterr = DTERR_BAD_FORMAT;
else
dterr = ParseDateTime(str, lowstr, field, ftype, MAXDATEFIELDS, &nf);
if (dterr == 0)
dterr = DecodeInterval(field, ftype, nf, &dtype, tm, &fsec);
if (dterr != 0)
{
if (dterr == DTERR_FIELD_OVERFLOW)
dterr = DTERR_INTERVAL_OVERFLOW;
DateTimeParseError(dterr, str, "interval");
}
result = (Interval *) palloc(sizeof(Interval));
......
......@@ -9,7 +9,7 @@
* Portions Copyright (c) 1996-2003, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*
* $Id: datetime.h,v 1.44 2003/08/05 18:30:21 tgl Exp $
* $Id: datetime.h,v 1.45 2003/08/27 23:29:29 tgl Exp $
*
*-------------------------------------------------------------------------
*/
......@@ -204,7 +204,7 @@ typedef struct
*/
#define FMODULO(t,q,u) \
do { \
q = ((t < 0) ? ceil(t / u): floor(t / u)); \
q = ((t < 0) ? ceil(t / u) : floor(t / u)); \
if (q != 0) t -= rint(q * u); \
} while(0)
......@@ -222,7 +222,7 @@ do { \
#else
#define TMODULO(t,q,u) \
do { \
q = ((t < 0) ? ceil(t / u): floor(t / u)); \
q = ((t < 0) ? ceil(t / u) : floor(t / u)); \
if (q != 0) t -= rint(q * u); \
} while(0)
#endif
......@@ -253,6 +253,15 @@ extern int day_tab[2][13];
|| (((m) == JULIAN_MINMONTH) && ((d) >= JULIAN_MINDAY))))) \
&& ((y) < JULIAN_MAXYEAR))
/* Julian-date equivalents of Day 0 in Unix and Postgres reckoning */
#define UNIX_EPOCH_JDATE 2440588 /* == date2j(1970, 1, 1) */
#define POSTGRES_EPOCH_JDATE 2451545 /* == date2j(2000, 1, 1) */
/*
* Info about limits of the Unix time_t data type. We assume that time_t
* is a signed int32 with origin 1970-01-01. Note this is only relevant
* when we use the C library's time routines for timezone processing.
*/
#define UTIME_MINYEAR (1901)
#define UTIME_MINMONTH (12)
#define UTIME_MINDAY (14)
......@@ -267,9 +276,17 @@ extern int day_tab[2][13];
|| (((y) == UTIME_MAXYEAR) && (((m) < UTIME_MAXMONTH) \
|| (((m) == UTIME_MAXMONTH) && ((d) <= UTIME_MAXDAY))))))
/* Julian-date equivalents of Day 0 in Unix and Postgres reckoning */
#define UNIX_EPOCH_JDATE 2440588 /* == date2j(1970, 1, 1) */
#define POSTGRES_EPOCH_JDATE 2451545 /* == date2j(2000, 1, 1) */
/*
* Datetime input parsing routines (ParseDateTime, DecodeDateTime, etc)
* return zero or a positive value on success. On failure, they return
* one of these negative code values. DateTimeParseError may be used to
* produce a correct ereport.
*/
#define DTERR_BAD_FORMAT (-1)
#define DTERR_FIELD_OVERFLOW (-2)
#define DTERR_MD_FIELD_OVERFLOW (-3) /* triggers hint about DateStyle */
#define DTERR_INTERVAL_OVERFLOW (-4)
#define DTERR_TZDISP_OVERFLOW (-5)
extern void GetCurrentDateTime(struct tm * tm);
......@@ -283,14 +300,14 @@ extern int ParseDateTime(const char *timestr, char *lowstr,
extern int DecodeDateTime(char **field, int *ftype,
int nf, int *dtype,
struct tm * tm, fsec_t *fsec, int *tzp);
extern int DecodeTimeOnly(char **field, int *ftype,
int nf, int *dtype,
struct tm * tm, fsec_t *fsec, int *tzp);
extern int DecodeInterval(char **field, int *ftype,
int nf, int *dtype,
struct tm * tm, fsec_t *fsec);
extern void DateTimeParseError(int dterr, const char *str,
const char *datatype);
extern int DetermineLocalTimeZone(struct tm * tm);
......
......@@ -28,9 +28,10 @@ INSERT INTO ABSTIME_TBL (f1) VALUES (abstime '-infinity');
INSERT INTO ABSTIME_TBL (f1) VALUES (abstime 'May 10, 1947 23:59:12');
-- what happens if we specify slightly misformatted abstime?
INSERT INTO ABSTIME_TBL (f1) VALUES ('Feb 35, 1946 10:00:00');
ERROR: invalid input syntax for abstime: "Feb 35, 1946 10:00:00"
ERROR: date/time field value out of range: "Feb 35, 1946 10:00:00"
HINT: Perhaps you need a different DateStyle setting.
INSERT INTO ABSTIME_TBL (f1) VALUES ('Feb 28, 1984 25:08:10');
ERROR: invalid input syntax for abstime: "Feb 28, 1984 25:08:10"
ERROR: date/time field value out of range: "Feb 28, 1984 25:08:10"
-- badly formatted abstimes: these should result in invalid abstimes
INSERT INTO ABSTIME_TBL (f1) VALUES ('bad date format');
ERROR: invalid input syntax for abstime: "bad date format"
......
......@@ -28,9 +28,10 @@ INSERT INTO ABSTIME_TBL (f1) VALUES (abstime '-infinity');
INSERT INTO ABSTIME_TBL (f1) VALUES (abstime 'May 10, 1947 23:59:12');
-- what happens if we specify slightly misformatted abstime?
INSERT INTO ABSTIME_TBL (f1) VALUES ('Feb 35, 1946 10:00:00');
ERROR: invalid input syntax for abstime: "Feb 35, 1946 10:00:00"
ERROR: date/time field value out of range: "Feb 35, 1946 10:00:00"
HINT: Perhaps you need a different DateStyle setting.
INSERT INTO ABSTIME_TBL (f1) VALUES ('Feb 28, 1984 25:08:10');
ERROR: invalid input syntax for abstime: "Feb 28, 1984 25:08:10"
ERROR: date/time field value out of range: "Feb 28, 1984 25:08:10"
-- badly formatted abstimes: these should result in invalid abstimes
INSERT INTO ABSTIME_TBL (f1) VALUES ('bad date format');
ERROR: invalid input syntax for abstime: "bad date format"
......
......@@ -10,7 +10,7 @@ INSERT INTO DATE_TBL VALUES ('1996-03-01');
INSERT INTO DATE_TBL VALUES ('1996-03-02');
INSERT INTO DATE_TBL VALUES ('1997-02-28');
INSERT INTO DATE_TBL VALUES ('1997-02-29');
ERROR: invalid input syntax for date: "1997-02-29"
ERROR: date/time field value out of range: "1997-02-29"
INSERT INTO DATE_TBL VALUES ('1997-03-01');
INSERT INTO DATE_TBL VALUES ('1997-03-02');
INSERT INTO DATE_TBL VALUES ('2000-04-01');
......
......@@ -81,7 +81,8 @@ SELECT timestamp with time zone '12/27/2001 04:05:06.789-08';
-- should fail in mdy mode:
SELECT timestamp with time zone '27/12/2001 04:05:06.789-08';
ERROR: invalid input syntax for timestamp with time zone: "27/12/2001 04:05:06.789-08"
ERROR: date/time field value out of range: "27/12/2001 04:05:06.789-08"
HINT: Perhaps you need a different DateStyle setting.
set datestyle to dmy;
SELECT timestamp with time zone '27/12/2001 04:05:06.789-08';
timestamptz
......
......@@ -81,7 +81,8 @@ SELECT timestamp with time zone '12/27/2001 04:05:06.789-08';
-- should fail in mdy mode:
SELECT timestamp with time zone '27/12/2001 04:05:06.789-08';
ERROR: invalid input syntax for timestamp with time zone: "27/12/2001 04:05:06.789-08"
ERROR: date/time field value out of range: "27/12/2001 04:05:06.789-08"
HINT: Perhaps you need a different DateStyle setting.
set datestyle to dmy;
SELECT timestamp with time zone '27/12/2001 04:05:06.789-08';
timestamptz
......
......@@ -81,7 +81,8 @@ SELECT timestamp with time zone '12/27/2001 04:05:06.789-08';
-- should fail in mdy mode:
SELECT timestamp with time zone '27/12/2001 04:05:06.789-08';
ERROR: invalid input syntax for timestamp with time zone: "27/12/2001 04:05:06.789-08"
ERROR: date/time field value out of range: "27/12/2001 04:05:06.789-08"
HINT: Perhaps you need a different DateStyle setting.
set datestyle to dmy;
SELECT timestamp with time zone '27/12/2001 04:05:06.789-08';
timestamptz
......
......@@ -128,7 +128,7 @@ INSERT INTO TIMESTAMP_TBL VALUES ('Dec 31 17:32:01 1996');
INSERT INTO TIMESTAMP_TBL VALUES ('Jan 01 17:32:01 1997');
INSERT INTO TIMESTAMP_TBL VALUES ('Feb 28 17:32:01 1997');
INSERT INTO TIMESTAMP_TBL VALUES ('Feb 29 17:32:01 1997');
ERROR: invalid input syntax for timestamp: "Feb 29 17:32:01 1997"
ERROR: date/time field value out of range: "Feb 29 17:32:01 1997"
INSERT INTO TIMESTAMP_TBL VALUES ('Mar 01 17:32:01 1997');
INSERT INTO TIMESTAMP_TBL VALUES ('Dec 30 17:32:01 1997');
INSERT INTO TIMESTAMP_TBL VALUES ('Dec 31 17:32:01 1997');
......@@ -138,7 +138,7 @@ INSERT INTO TIMESTAMP_TBL VALUES ('Dec 31 17:32:01 2000');
INSERT INTO TIMESTAMP_TBL VALUES ('Jan 01 17:32:01 2001');
-- Currently unsupported syntax and ranges
INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 -0097');
ERROR: invalid input syntax for timestamp: "Feb 16 17:32:01 -0097"
ERROR: time zone displacement out of range: "Feb 16 17:32:01 -0097"
INSERT INTO TIMESTAMP_TBL VALUES ('Feb 16 17:32:01 5097 BC');
ERROR: timestamp out of range: "Feb 16 17:32:01 5097 BC"
SELECT '' AS "64", d1 FROM TIMESTAMP_TBL;
......
......@@ -123,7 +123,7 @@ INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 31 17:32:01 1996');
INSERT INTO TIMESTAMPTZ_TBL VALUES ('Jan 01 17:32:01 1997');
INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 28 17:32:01 1997');
INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 29 17:32:01 1997');
ERROR: invalid input syntax for timestamp with time zone: "Feb 29 17:32:01 1997"
ERROR: date/time field value out of range: "Feb 29 17:32:01 1997"
INSERT INTO TIMESTAMPTZ_TBL VALUES ('Mar 01 17:32:01 1997');
INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 30 17:32:01 1997');
INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 31 17:32:01 1997');
......@@ -133,7 +133,7 @@ INSERT INTO TIMESTAMPTZ_TBL VALUES ('Dec 31 17:32:01 2000');
INSERT INTO TIMESTAMPTZ_TBL VALUES ('Jan 01 17:32:01 2001');
-- Currently unsupported syntax and ranges
INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 -0097');
ERROR: invalid input syntax for timestamp with time zone: "Feb 16 17:32:01 -0097"
ERROR: time zone displacement out of range: "Feb 16 17:32:01 -0097"
INSERT INTO TIMESTAMPTZ_TBL VALUES ('Feb 16 17:32:01 5097 BC');
ERROR: timestamp out of range: "Feb 16 17:32:01 5097 BC"
SELECT '' AS "64", d1 FROM TIMESTAMPTZ_TBL;
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册