diff --git a/.gitea/workflows/test-cockroach.yml b/.gitea/workflows/test-cockroach.yml index 4e547267..37874e3e 100644 --- a/.gitea/workflows/test-cockroach.yml +++ b/.gitea/workflows/test-cockroach.yml @@ -1,9 +1,11 @@ name: test cockroach -on: +on: push: branches: - - master - pull_request: + - donttrigger # disabled for now + #- main + #- v1 + #pull_request: jobs: test-cockroach: @@ -26,8 +28,6 @@ jobs: services: cockroach: image: cockroachdb/cockroach:v19.2.4 - ports: - - 26257:26257 cmd: - 'start' - '--insecure' diff --git a/.gitea/workflows/test-mariadb.yml b/.gitea/workflows/test-mariadb.yml index 0ddfcf58..2b610d2b 100644 --- a/.gitea/workflows/test-mariadb.yml +++ b/.gitea/workflows/test-mariadb.yml @@ -2,7 +2,8 @@ name: test mariadb on: push: branches: - - master + - main + - v1 pull_request: jobs: @@ -28,6 +29,4 @@ jobs: image: mariadb:10.4 env: MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: xorm_test - ports: - - 3306:3306 \ No newline at end of file + MYSQL_DATABASE: xorm_test \ No newline at end of file diff --git a/.gitea/workflows/test-mssql-collation.yml b/.gitea/workflows/test-mssql-collation.yml index 3d6d7ad1..979e1bb0 100644 --- a/.gitea/workflows/test-mssql-collation.yml +++ b/.gitea/workflows/test-mssql-collation.yml @@ -2,7 +2,8 @@ name: test mssql on: push: branches: - - master + - main + - v1 pull_request: jobs: @@ -29,6 +30,4 @@ jobs: env: ACCEPT_EULA: Y SA_PASSWORD: yourStrong(!)Password - MSSQL_PID: Standard - ports: - - 1433:1433 \ No newline at end of file + MSSQL_PID: Standard \ No newline at end of file diff --git a/.gitea/workflows/test-mssql.yml b/.gitea/workflows/test-mssql.yml index 027ba5e3..cb155699 100644 --- a/.gitea/workflows/test-mssql.yml +++ b/.gitea/workflows/test-mssql.yml @@ -2,7 +2,8 @@ name: test mssql on: push: branches: - - master + - main + - v1 pull_request: jobs: @@ -28,6 +29,4 @@ jobs: env: ACCEPT_EULA: Y SA_PASSWORD: yourStrong(!)Password - MSSQL_PID: Standard - ports: - - 1433:1433 \ No newline at end of file + MSSQL_PID: Standard \ No newline at end of file diff --git a/.gitea/workflows/test-mysql.yml b/.gitea/workflows/test-mysql.yml index d3eabf14..198f8980 100644 --- a/.gitea/workflows/test-mysql.yml +++ b/.gitea/workflows/test-mysql.yml @@ -2,7 +2,8 @@ name: test mysql on: push: branches: - - master + - main + - v1 pull_request: jobs: @@ -28,6 +29,4 @@ jobs: image: mysql:5.7 env: MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: xorm_test - ports: - - 3306:3306 \ No newline at end of file + MYSQL_DATABASE: xorm_test \ No newline at end of file diff --git a/.gitea/workflows/test-mysql8.yml b/.gitea/workflows/test-mysql8.yml index 8318c345..252d28ff 100644 --- a/.gitea/workflows/test-mysql8.yml +++ b/.gitea/workflows/test-mysql8.yml @@ -2,7 +2,8 @@ name: test mysql8 on: push: branches: - - master + - main + - v1 pull_request: jobs: @@ -28,6 +29,4 @@ jobs: image: mysql:8.0 env: MYSQL_ALLOW_EMPTY_PASSWORD: yes - MYSQL_DATABASE: xorm_test - ports: - - 3306:3306 \ No newline at end of file + MYSQL_DATABASE: xorm_test \ No newline at end of file diff --git a/.gitea/workflows/test-postgres.yml b/.gitea/workflows/test-postgres.yml index 18e2cb4a..f05b78f6 100644 --- a/.gitea/workflows/test-postgres.yml +++ b/.gitea/workflows/test-postgres.yml @@ -2,7 +2,8 @@ name: test postgres on: push: branches: - - master + - main + - v1 pull_request: jobs: @@ -51,6 +52,4 @@ jobs: env: POSTGRES_DB: xorm_test POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - ports: - - 5432:5432 \ No newline at end of file + POSTGRES_PASSWORD: postgres \ No newline at end of file diff --git a/.gitea/workflows/test-sqlite.yml b/.gitea/workflows/test-sqlite.yml index 2fdf7410..5b64f025 100644 --- a/.gitea/workflows/test-sqlite.yml +++ b/.gitea/workflows/test-sqlite.yml @@ -2,7 +2,8 @@ name: test sqlite on: push: branches: - - master + - main + - v1 pull_request: jobs: @@ -23,4 +24,14 @@ jobs: - name: test sqlite3 run: make test-sqlite3 - name: test sqlite3 with cache - run: TEST_CACHE_ENABLE=true make test-sqlite3 \ No newline at end of file + run: TEST_CACHE_ENABLE=true make test-sqlite3 + + govulncheck_job: + runs-on: ubuntu-latest + name: Run govulncheck + steps: + - id: govulncheck + uses: golang/govulncheck-action@v1 + with: + go-version-file: 'go.mod' + go-package: ./... \ No newline at end of file diff --git a/.gitea/workflows/test-tidb.yml b/.gitea/workflows/test-tidb.yml index 0d87e404..6a02e348 100644 --- a/.gitea/workflows/test-tidb.yml +++ b/.gitea/workflows/test-tidb.yml @@ -2,7 +2,8 @@ name: test tidb on: push: branches: - - master + - main + - v1 pull_request: jobs: @@ -24,6 +25,4 @@ jobs: services: tidb: - image: pingcap/tidb:v3.0.3 - ports: - - 4000:4000 \ No newline at end of file + image: pingcap/tidb:v3.0.3 \ No newline at end of file diff --git a/README.md b/README.md index 5e6418df..3eb99321 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Xorm is a simple and powerful ORM for Go. -[![Build Status](https://drone.gitea.com/api/badges/xorm/xorm/status.svg)](https://drone.gitea.com/xorm/xorm) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) [![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) [![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3) +[![Build Status](https://gitea.com/xorm/xorm/actions/workflows/release-tag/badge.svg)](https://gitea.com/xorm/xorm/actions) [![](http://gocover.io/_badge/xorm.io/xorm)](https://gocover.io/xorm.io/xorm) [![](https://goreportcard.com/badge/xorm.io/xorm)](https://goreportcard.com/report/xorm.io/xorm) [![Join the chat at https://img.shields.io/discord/323460943201959939.svg](https://img.shields.io/discord/323460943201959939.svg)](https://discord.gg/HuR2CF3) ## Notice @@ -50,6 +50,9 @@ Drivers for Go's sql package which currently support database/sql includes: * MsSql - [github.com/microsoft/go-mssqldb](https://github.com/microsoft/go-mssqldb) +* GBase8s + - [https://gitee.com/GBase8s/go-gci](https://gitee.com/GBase8s/go-gci) + * Oracle - [github.com/godror/godror](https://github.com/godror/godror) (experiment) - [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (experiment) @@ -465,7 +468,7 @@ res, err := engine.Transaction(func(session *xorm.Session) (interface{}, error) ## Contributing -If you want to pull request, please see [CONTRIBUTING](https://gitea.com/xorm/xorm/src/branch/master/CONTRIBUTING.md). And you can also go to [Xorm on discourse](https://xorm.discourse.group) to discuss. +If you want to pull request, please see [CONTRIBUTING](CONTRIBUTING.md). And you can also go to [Xorm on discourse](https://xorm.discourse.group) to discuss. ## Credits diff --git a/README_CN.md b/README_CN.md index ba18755e..985a7293 100644 --- a/README_CN.md +++ b/README_CN.md @@ -49,6 +49,9 @@ v1.0.0 相对于 v0.8.2 有以下不兼容的变更: * MsSql - [github.com/microsoft/go-mssqldb](https://github.com/microsoft/go-mssqldb) +* GBase8s + - [https://gitee.com/GBase8s/go-gci](https://gitee.com/GBase8s/go-gci) + * Oracle - [github.com/godror/godror](https://github.com/godror/godror) (试验性支持) - [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (试验性支持) diff --git a/dialects/dialect.go b/dialects/dialect.go index 8e512c4f..f907c507 100644 --- a/dialects/dialect.go +++ b/dialects/dialect.go @@ -279,6 +279,7 @@ func regDrvsNDialects() bool { "pgx": {"postgres", func() Driver { return &pqDriverPgx{} }, func() Dialect { return &postgres{} }}, "sqlite3": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, "sqlite": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, + "libsql": {"sqlite3", func() Driver { return &sqlite3Driver{} }, func() Dialect { return &sqlite3{} }}, "oci8": {"oracle", func() Driver { return &oci8Driver{} }, func() Dialect { return &oracle{} }}, "godror": {"oracle", func() Driver { return &godrorDriver{} }, func() Dialect { return &oracle{} }}, "oracle": {"oracle", func() Driver { return &oracleDriver{} }, func() Dialect { return &oracle{} }}, diff --git a/dialects/gbase8s.go b/dialects/gbase8s.go new file mode 100644 index 00000000..05ab7790 --- /dev/null +++ b/dialects/gbase8s.go @@ -0,0 +1,1089 @@ +// Copyright 2025 The Xorm Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package dialects + +import ( + "context" + "database/sql" + "errors" + "fmt" + "regexp" + "strconv" + "strings" + + "xorm.io/xorm/core" + "xorm.io/xorm/schemas" +) + +func init() { + RegisterDriver("gbase8s", &gbase8sDriver{}) + RegisterDialect(schemas.GBASE8S, func() Dialect { + return &gbase8s{} + }) +} + +var ( + gbase8sReservedWords = map[string]bool{ + "ACCESS": true, + "ACCOUNT": true, + "ACTIVATE": true, + "ADD": true, + "ADMIN": true, + "ADVISE": true, + "AFTER": true, + "ALL": true, + "ALL_ROWS": true, + "ALLOCATE": true, + "ALTER": true, + "ANALYZE": true, + "AND": true, + "ANY": true, + "ARCHIVE": true, + "ARCHIVELOG": true, + "ARRAY": true, + "AS": true, + "ASC": true, + "AT": true, + "AUDIT": true, + "AUTHENTICATED": true, + "AUTHORIZATION": true, + "AUTOEXTEND": true, + "AUTOMATIC": true, + "BACKUP": true, + "BECOME": true, + "BEFORE": true, + "BEGIN": true, + "BETWEEN": true, + "BFILE": true, + "BITMAP": true, + "BLOB": true, + "BLOCK": true, + "BODY": true, + "BY": true, + "CACHE": true, + "CACHE_INSTANCES": true, + "CANCEL": true, + "CASCADE": true, + "CAST": true, + "CFILE": true, + "CHAINED": true, + "CHANGE": true, + "CHAR": true, + "CHAR_CS": true, + "CHARACTER": true, + "CHECK": true, + "CHECKPOINT": true, + "CHOOSE": true, + "CHUNK": true, + "CLEAR": true, + "CLOB": true, + "CLONE": true, + "CLOSE": true, + "CLOSE_CACHED_OPEN_CURSORS": true, + "CLUSTER": true, + "COALESCE": true, + "COLUMN": true, + "COLUMNS": true, + "COMMENT": true, + "COMMIT": true, + "COMMITTED": true, + "COMPATIBILITY": true, + "COMPILE": true, + "COMPLETE": true, + "COMPOSITE_LIMIT": true, + "COMPRESS": true, + "COMPUTE": true, + "CONNECT": true, + "CONNECT_TIME": true, + "CONSTRAINT": true, + "CONSTRAINTS": true, + "CONTENTS": true, + "CONTINUE": true, + "CONTROLFILE": true, + "CONVERT": true, + "COST": true, + "CPU_PER_CALL": true, + "CPU_PER_SESSION": true, + "CREATE": true, + "CURRENT": true, + "CURRENT_SCHEMA": true, + "CURREN_USER": true, + "CURSOR": true, + "CYCLE": true, + "DANGLING": true, + "DATABASE": true, + "DATAFILE": true, + "DATAFILES": true, + "DATAOBJNO": true, + "DATE": true, + "DBA": true, + "DBHIGH": true, + "DBLOW": true, + "DBMAC": true, + "DEALLOCATE": true, + "DEBUG": true, + "DEC": true, + "DECIMAL": true, + "DECLARE": true, + "DEFAULT": true, + "DEFERRABLE": true, + "DEFERRED": true, + "DEGREE": true, + "DELETE": true, + "DEREF": true, + "DESC": true, + "DIRECTORY": true, + "DISABLE": true, + "DISCONNECT": true, + "DISMOUNT": true, + "DISTINCT": true, + "DISTRIBUTED": true, + "DML": true, + "DOUBLE": true, + "DROP": true, + "DUMP": true, + "EACH": true, + "ELSE": true, + "ENABLE": true, + "END": true, + "ENFORCE": true, + "ENTRY": true, + "ESCAPE": true, + "EXCEPT": true, + "EXCEPTIONS": true, + "EXCHANGE": true, + "EXCLUDING": true, + "EXCLUSIVE": true, + "EXECUTE": true, + "EXISTS": true, + "EXPIRE": true, + "EXPLAIN": true, + "EXTENT": true, + "EXTENTS": true, + "EXTERNALLY": true, + "FAILED_LOGIN_ATTEMPTS": true, + "FALSE": true, + "FAST": true, + "FILE": true, + "FIRST_ROWS": true, + "FLAGGER": true, + "FLOAT": true, + "FLOB": true, + "FLUSH": true, + "FOR": true, + "FORCE": true, + "FOREIGN": true, + "FREELIST": true, + "FREELISTS": true, + "FROM": true, + "FULL": true, + "FUNCTION": true, + "GLOBAL": true, + "GLOBALLY": true, + "GLOBAL_NAME": true, + "GRANT": true, + "GROUP": true, + "GROUPS": true, + "HASH": true, + "HASHKEYS": true, + "HAVING": true, + "HEADER": true, + "HEAP": true, + "IDENTIFIED": true, + "IDGENERATORS": true, + "IDLE_TIME": true, + "IF": true, + "IMMEDIATE": true, + "IN": true, + "INCLUDING": true, + "INCREMENT": true, + "INDEX": true, + "INDEXED": true, + "INDEXES": true, + "INDICATOR": true, + "IND_PARTITION": true, + "INITIAL": true, + "INITIALLY": true, + "INITRANS": true, + "INSERT": true, + "INSTANCE": true, + "INSTANCES": true, + "INSTEAD": true, + "INT": true, + "INTEGER": true, + "INTERMEDIATE": true, + "INTERSECT": true, + "INTO": true, + "IS": true, + "ISOLATION": true, + "ISOLATION_LEVEL": true, + "KEEP": true, + "KEY": true, + "KILL": true, + "LABEL": true, + "LAYER": true, + "LESS": true, + "LEVEL": true, + "LIBRARY": true, + "LIKE": true, + "LIMIT": true, + "LINK": true, + "LIST": true, + "LOB": true, + "LOCAL": true, + "LOCK": true, + "LOCKED": true, + "LOG": true, + "LOGFILE": true, + "LOGGING": true, + "LOGICAL_READS_PER_CALL": true, + "LOGICAL_READS_PER_SESSION": true, + "LONG": true, + "MANAGE": true, + "MASTER": true, + "MAX": true, + "MAXARCHLOGS": true, + "MAXDATAFILES": true, + "MAXEXTENTS": true, + "MAXINSTANCES": true, + "MAXLOGFILES": true, + "MAXLOGHISTORY": true, + "MAXLOGMEMBERS": true, + "MAXSIZE": true, + "MAXTRANS": true, + "MAXVALUE": true, + "MIN": true, + "MEMBER": true, + "MINIMUM": true, + "MINEXTENTS": true, + "MINUS": true, + "MINVALUE": true, + "MLSLABEL": true, + "MLS_LABEL_FORMAT": true, + "MODE": true, + "MODIFY": true, + "MOUNT": true, + "MOVE": true, + "MTS_DISPATCHERS": true, + "MULTISET": true, + "NATIONAL": true, + "NCHAR": true, + "NCHAR_CS": true, + "NCLOB": true, + "NEEDED": true, + "NESTED": true, + "NETWORK": true, + "NEW": true, + "NEXT": true, + "NOARCHIVELOG": true, + "NOAUDIT": true, + "NOCACHE": true, + "NOCOMPRESS": true, + "NOCYCLE": true, + "NOFORCE": true, + "NOLOGGING": true, + "NOMAXVALUE": true, + "NOMINVALUE": true, + "NONE": true, + "NOORDER": true, + "NOOVERRIDE": true, + "NOPARALLEL": true, + "NOREVERSE": true, + "NORMAL": true, + "NOSORT": true, + "NOT": true, + "NOTHING": true, + "NOWAIT": true, + "NULL": true, + "NUMBER": true, + "NUMERIC": true, + "NVARCHAR2": true, + "OBJECT": true, + "OBJNO": true, + "OBJNO_REUSE": true, + "OF": true, + "OFF": true, + "OFFLINE": true, + "OID": true, + "OIDINDEX": true, + "OLD": true, + "ON": true, + "ONLINE": true, + "ONLY": true, + "OPCODE": true, + "OPEN": true, + "OPTIMAL": true, + "OPTIMIZER_GOAL": true, + "OPTION": true, + "OR": true, + "ORDER": true, + "ORGANIZATION": true, + "OSLABEL": true, + "OVERFLOW": true, + "OWN": true, + "PACKAGE": true, + "PARALLEL": true, + "PARTITION": true, + "PASSWORD": true, + "PASSWORD_GRACE_TIME": true, + "PASSWORD_LIFE_TIME": true, + "PASSWORD_LOCK_TIME": true, + "PASSWORD_REUSE_MAX": true, + "PASSWORD_REUSE_TIME": true, + "PASSWORD_VERIFY_FUNCTION": true, + "PCTFREE": true, + "PCTINCREASE": true, + "PCTTHRESHOLD": true, + "PCTUSED": true, + "PCTVERSION": true, + "PERCENT": true, + "PERMANENT": true, + "PLAN": true, + "PLSQL_DEBUG": true, + "POST_TRANSACTION": true, + "PRECISION": true, + "PRESERVE": true, + "PRIMARY": true, + "PRIOR": true, + "PRIVATE": true, + "PRIVATE_SGA": true, + "PRIVILEGE": true, + "PRIVILEGES": true, + "PROCEDURE": true, + "PROFILE": true, + "PUBLIC": true, + "PURGE": true, + "QUEUE": true, + "QUOTA": true, + "RANGE": true, + "RAW": true, + "RBA": true, + "READ": true, + "READUP": true, + "REAL": true, + "REBUILD": true, + "RECOVER": true, + "RECOVERABLE": true, + "RECOVERY": true, + "REF": true, + "REFERENCES": true, + "REFERENCING": true, + "REFRESH": true, + "RENAME": true, + "REPLACE": true, + "RESET": true, + "RESETLOGS": true, + "RESIZE": true, + "RESOURCE": true, + "RESTRICTED": true, + "RETURN": true, + "RETURNING": true, + "REUSE": true, + "REVERSE": true, + "REVOKE": true, + "ROLE": true, + "ROLES": true, + "ROLLBACK": true, + "ROW": true, + "ROWID": true, + "ROWNUM": true, + "ROWS": true, + "RULE": true, + "SAMPLE": true, + "SAVEPOINT": true, + "SB4": true, + "SCAN_INSTANCES": true, + "SCHEMA": true, + "SCN": true, + "SCOPE": true, + "SD_ALL": true, + "SD_INHIBIT": true, + "SD_SHOW": true, + "SEGMENT": true, + "SEG_BLOCK": true, + "SEG_FILE": true, + "SELECT": true, + "SEQUENCE": true, + "SERIALIZABLE": true, + "SESSION": true, + "SESSION_CACHED_CURSORS": true, + "SESSIONS_PER_USER": true, + "SET": true, + "SHARE": true, + "SHARED": true, + "SHARED_POOL": true, + "SHRINK": true, + "SIZE": true, + "SKIP": true, + "SKIP_UNUSABLE_INDEXES": true, + "SMALLINT": true, + "SNAPSHOT": true, + "SOME": true, + "SORT": true, + "SPECIFICATION": true, + "SPLIT": true, + "SQL_TRACE": true, + "STANDBY": true, + "START": true, + "STATEMENT_ID": true, + "STATISTICS": true, + "STOP": true, + "STORAGE": true, + "STORE": true, + "STRUCTURE": true, + "SUCCESSFUL": true, + "SWITCH": true, + "SYS_OP_ENFORCE_NOT_NULL$": true, + "SYS_OP_NTCIMG$": true, + "SYNONYM": true, + "SYSDATE": true, + "SYSDBA": true, + "SYSOPER": true, + "SYSTEM": true, + "TABLE": true, + "TABLES": true, + "TABLESPACE": true, + "TABLESPACE_NO": true, + "TABNO": true, + "TEMPORARY": true, + "THAN": true, + "THE": true, + "THEN": true, + "THREAD": true, + "TIMESTAMP": true, + "TIME": true, + "TO": true, + "TOPLEVEL": true, + "TRACE": true, + "TRACING": true, + "TRANSACTION": true, + "TRANSITIONAL": true, + "TRIGGER": true, + "TRIGGERS": true, + "TRUE": true, + "TRUNCATE": true, + "TX": true, + "TYPE": true, + "UB2": true, + "UBA": true, + "UID": true, + "UNARCHIVED": true, + "UNDO": true, + "UNION": true, + "UNIQUE": true, + "UNLIMITED": true, + "UNLOCK": true, + "UNRECOVERABLE": true, + "UNTIL": true, + "UNUSABLE": true, + "UNUSED": true, + "UPDATABLE": true, + "UPDATE": true, + "USAGE": true, + "USE": true, + "USER": true, + "USING": true, + "VALIDATE": true, + "VALIDATION": true, + "VALUE": true, + "VALUES": true, + "VARCHAR": true, + "VARCHAR2": true, + "VARYING": true, + "VIEW": true, + "WHEN": true, + "WHENEVER": true, + "WHERE": true, + "WITH": true, + "WITHOUT": true, + "WORK": true, + "WRITE": true, + "WRITEDOWN": true, + "WRITEUP": true, + "XID": true, + "YEAR": true, + "ZONE": true, + } + + gbase8sQuoter = schemas.Quoter{ + Prefix: '"', + Suffix: '"', + IsReserved: schemas.AlwaysReserve, + } +) + +type gbase8s struct { + Base +} + +func (db *gbase8s) Init(uri *URI) error { + db.quoter = gbase8sQuoter + return db.Base.Init(db, uri) +} + +func (db *gbase8s) Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) { + rows, err := queryer.QueryContext(ctx, "SELECT dbinfo('version', 'full') FROM systables WHERE tabid = 1") + if err != nil { + return nil, err + } + defer rows.Close() + + var version string + if !rows.Next() { + if rows.Err() != nil { + return nil, rows.Err() + } + return nil, errors.New("unknown version") + } + if err := rows.Scan(&version); err != nil { + return nil, err + } + // Parse GBase 8s version string Example: "GBase Server Version 12.10.FC4G1TL" + fields := strings.Fields(version) + if len(fields) >= 4 && fields[0] == "GBase" && fields[1] == "Server" { + version = fields[3] + } + return &schemas.Version{ + Number: version, + Edition: "GBase 8s", + }, nil +} + +func (db *gbase8s) Features() *DialectFeatures { + return &DialectFeatures{ + AutoincrMode: IncrAutoincrMode, + } +} + +func (db *gbase8s) SQLType(c *schemas.Column) string { + var res string + switch t := c.SQLType.Name; t { + case schemas.Serial: + c.IsAutoIncrement = true + res = "SERIAL" + case schemas.BigSerial: + c.IsAutoIncrement = true + res = "BIGSERIAL" + case schemas.TinyInt, schemas.UnsignedTinyInt, schemas.SmallInt, schemas.Bit, schemas.UnsignedBit: + res = "SMALLINT" + case schemas.UnsignedSmallInt, schemas.MediumInt, schemas.UnsignedMediumInt, schemas.Int, schemas.Integer: + if c.IsAutoIncrement { + return "SERIAL" + } + return "INT" + case schemas.UnsignedInt, schemas.BigInt, schemas.UnsignedBigInt: + if c.IsAutoIncrement { + return "BIGSERIAL" + } + return "BIGINT" + case schemas.Real: + res = " SMALLFLOAT" // "REAL" + case schemas.UnsignedFloat, schemas.Double, schemas.Float: + res = "FLOAT" // "DOUBLE PRECISION" + case schemas.Decimal, schemas.Numeric, schemas.Number: + res = "NUMERIC" // DECIMAL + case schemas.Money, schemas.SmallMoney: + res = "Money" + case schemas.Bool, schemas.Boolean: + switch c.Default { + case "true": + c.Default = "1" + case "false": + c.Default = "0" + } + res = "NUMERIC(1,0)" + case schemas.Char, schemas.NChar, schemas.Uuid: + res = "CHAR" + case schemas.Varchar, schemas.NVarchar, schemas.VARCHAR2, schemas.NVarchar: + res = "VARCHAR" + case schemas.Enum, schemas.Set: + res = "VARCHAR(255)" + case schemas.TinyText, schemas.MediumText, schemas.Text, schemas.LongText, schemas.XML, schemas.Array: + res = "TEXT" + case schemas.Clob: + res = "CLOB" + case schemas.TinyBlob, schemas.MediumBlob, schemas.LongBlob, schemas.Blob, schemas.VarBinary, schemas.Bytea: + res = "BLOB" + case schemas.Binary: + res = "BYTE" + case schemas.Json, schemas.Jsonb: + res = "JSON" + case schemas.Date, schemas.Year: + res = "DATE" + case schemas.DateTime, schemas.SmallDateTime, schemas.TimeStamp, schemas.TimeStampz, schemas.Time: + if c.Length >= 1 && c.Length <= 5 { + return fmt.Sprintf("DATETIME YEAR TO FRACTION(%d)", c.Length) + } else { + return "DATETIME YEAR TO FRACTION(5)" + } + default: + res = t + } + hasLen1 := c.Length > 0 + hasLen2 := c.Length2 > 0 + if res == "BIGINT" || res == "INT" || res == "SMALLINT" { + // GBase 8s INT doesn't need length specification + } else if hasLen2 { + res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")" + } else if hasLen1 { + res += "(" + strconv.FormatInt(c.Length, 10) + ")" + } + return res +} + +func (db *gbase8s) ColumnTypeKind(t string) int { + t = strings.Replace(t, "SQLT_", "", 1) + switch strings.ToUpper(t) { + case "DATETIME", "DATE", "TIMESTAMP": + return schemas.TIME_TYPE + case "CHAR", "VARCHAR", "NCHAR", "NVARCHAR", "TEXT", "VARCHAR2", "NVARCHAR2", "LONG", "CLOB", "NCLOB": + return schemas.TEXT_TYPE + case "BLOB", "BYTE": + return schemas.BLOB_TYPE + case "BIGINT", "INTEGER", "SMALLINT", "INT", "FLOAT", "REAL", "DOUBLE", "DECIMAL", "NUMERIC", "MONEY", "SMALLFLOAT", "NUMBER": + return schemas.NUMERIC_TYPE + case "SERIAL", "BIGSERIAL": + return schemas.NUMERIC_TYPE + case "BOOLEAN": + return schemas.BOOL_TYPE + default: + return schemas.UNKNOW_TYPE + } +} + +func (db *gbase8s) IsReserved(name string) bool { + _, ok := gbase8sReservedWords[strings.ToUpper(name)] + return ok +} + +func (db *gbase8s) SetQuotePolicy(quotePolicy QuotePolicy) { + switch quotePolicy { + case QuotePolicyNone: + q := gbase8sQuoter + q.IsReserved = schemas.AlwaysNoReserve + db.quoter = q + case QuotePolicyReserved: + q := gbase8sQuoter + q.IsReserved = db.IsReserved + db.quoter = q + case QuotePolicyAlways: + fallthrough + default: + db.quoter = gbase8sQuoter + } +} + +func (db *gbase8s) AutoIncrStr() string { + return "" // "SERIAL" +} + +func (db *gbase8s) GetIndexes(queryer core.Queryer, ctx context.Context, tableName string) (map[string]*schemas.Index, error) { + s := `select col.colname AS column_name, idx.idxtype AS uniqueness, idx.idxname AS index_name FROM sysindexes idx + JOIN systables tab ON idx.tabid = tab.tabid + JOIN ( + SELECT tabid, idxname, part1 AS colno, 1 AS seq FROM sysindexes WHERE part1 > 0 + UNION ALL + SELECT tabid, idxname, part2, 2 FROM sysindexes WHERE part2 > 0 + UNION ALL + SELECT tabid, idxname, part3, 3 FROM sysindexes WHERE part3 > 0 + UNION ALL + SELECT tabid, idxname, part4, 4 FROM sysindexes WHERE part4 > 0 + ) AS part ON part.idxname = idx.idxname AND part.tabid = idx.tabid + JOIN syscolumns col ON col.tabid = idx.tabid AND col.colno = part.colno + WHERE tab.tabname =:1 ORDER BY idx.idxname, part.seq` + s = compressStr(s) + rows, err := queryer.QueryContext(ctx, s, tableName) + if err != nil { + return nil, err + } + defer rows.Close() + + proCols, err := db.primaryKeys(queryer, ctx, tableName) + if err != nil { + return nil, err + } + + indexes := make(map[string]*schemas.Index) + for rows.Next() { + var indexType int + var indexName, colName, uniqueness string + err = rows.Scan(&colName, &uniqueness, &indexName) + if err != nil { + return nil, err + } + indexName = strings.Trim(indexName, `" `) + if inSlice(colName, proCols) { + continue + } + var isRegular bool + if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) { + indexName = indexName[5+len(tableName):] + isRegular = true + } + if uniqueness == "U" { + indexType = schemas.UniqueType + } else { + indexType = schemas.IndexType + } + + var index *schemas.Index + var ok bool + if index, ok = indexes[indexName]; !ok { + index = new(schemas.Index) + index.Type = indexType + index.Name = indexName + index.IsRegular = isRegular + indexes[indexName] = index + } + index.AddColumn(colName) + } + if rows.Err() != nil { + return nil, rows.Err() + } + return indexes, nil +} + +func (db *gbase8s) IndexCheckSQL(tableName, idxName string) (string, []interface{}) { + args := []interface{}{tableName, idxName} + sql := `SELECT idx.idxname FROM sysindexes idx + JOIN systables tab ON idx.tabid = tab.tabid + JOIN syscolumns col ON col.tabid = tab.tabid + WHERE tab.tabname = :1 and idx.idxname = :2 AND col.colno IN ( + idx.part1, idx.part2, idx.part3, idx.part4, idx.part5, idx.part6, idx.part7, idx.part8, + idx.part9, idx.part10, idx.part11, idx.part12, idx.part13, idx.part14, idx.part15, idx.part16 + );` + sql = compressStr(sql) + return sql, args +} + +func (db *gbase8s) DropIndexSQL(tableName string, index *schemas.Index) string { + var name string + if index.IsRegular { + name = index.XName(tableName) + } else { + name = index.Name + } + return fmt.Sprintf("DROP INDEX %s", db.quoter.Quote(name)) +} + +func (db *gbase8s) GetTables(queryer core.Queryer, ctx context.Context) ([]*schemas.Table, error) { + s := "SELECT tabname FROM systables WHERE tabid >= 100 AND tabtype = 'T'" + rows, err := queryer.QueryContext(ctx, s) + if err != nil { + return nil, err + } + defer rows.Close() + + tables := make([]*schemas.Table, 0) + for rows.Next() { + table := schemas.NewEmptyTable() + err = rows.Scan(&table.Name) + if err != nil { + return nil, err + } + tables = append(tables, table) + } + if rows.Err() != nil { + return nil, rows.Err() + } + return tables, nil +} + +func (db *gbase8s) IsTableExist(queryer core.Queryer, ctx context.Context, tableName string) (bool, error) { + return db.HasRecords(queryer, ctx, `SELECT tabname FROM systables WHERE tabname = :1`, tableName) +} + +func (db *gbase8s) CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) { + sql := "CREATE TABLE " + if tableName == "" { + tableName = table.Name + } + quoter := db.Quoter() + sql += quoter.Quote(tableName) + " (" + pkList := table.PrimaryKeys + for _, colName := range table.ColumnsSeq() { + col := table.GetColumn(colName) + s, _ := ColumnString(db, col, false, false) + sql += s + if len(col.Comment) > 0 { + sql += fmt.Sprintf(" COMMENT '%s'", col.Comment) + } + sql = strings.TrimSpace(sql) + sql += ", " + } + if len(pkList) > 0 { + sql += "PRIMARY KEY ( " + sql += quoter.Join(pkList, ",") + sql += " ), " + } + sql = sql[:len(sql)-2] + ")" + return sql, false, nil +} + +func (db *gbase8s) DropTableSQL(tableName string) (string, bool) { + return fmt.Sprintf("DROP TABLE %s", db.quoter.Quote(tableName)), false +} + +func (db *gbase8s) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) { + s := `SELECT c.colname, c.coltypename, c.collength, MOD(c.collength, 256) AS scale, (c.collength - MOD(c.collength,256)) / 256 AS precision, + df.default, cm.comments, cs.constrtype FROM syscolumnsext c + left join systables t on c.tabid = t.tabid + left join syscolcomms cm on c.colno = cm.colno and c.tabid = cm.tabid + left join sysdefaultsexpr df on c.colno = df.colno and c.tabid = df.tabid and df.type = 'T' + left join syscoldepend nl on c.colno = nl.colno and c.tabid = nl.tabid + left join sysconstraints cs on nl.constrid = cs.constrid and c.tabid = cs.tabid + where t.tabname = :1` + s = compressStr(s) + rows, err := queryer.QueryContext(ctx, s, tableName) + if err != nil { + return nil, nil, err + } + defer rows.Close() + + proCols, err := db.primaryKeys(queryer, ctx, tableName) + if err != nil { + return nil, nil, err + } + + cols := make(map[string]*schemas.Column) + colSeq := make([]string, 0) + for rows.Next() { + col := new(schemas.Column) + col.Indexes = make(map[string]int) + var colName, colDefault, nullable, dataType, comment *string + var dataLen, precision, scale int64 + err = rows.Scan(&colName, &dataType, &dataLen, &scale, &precision, &colDefault, &comment, &nullable) + if err != nil { + return nil, nil, err + } + + col.Name = strings.Trim(*colName, `" `) + if colDefault != nil { + col.Default = *colDefault + col.DefaultIsEmpty = false + } else { + col.DefaultIsEmpty = true + } + if inSlice(col.Name, proCols) { + col.IsPrimaryKey = true + } + + if nullable != nil && *nullable == "N" { + col.Nullable = true + } else { + col.Nullable = false + } + if comment != nil { + col.Comment = *comment + } + + var ignore bool + var dt string + var len1, len2 int64 + dts := strings.Split(*dataType, "(") + dt = dts[0] + if len(dts) > 1 { + lens := strings.Split(dts[1][:len(dts[1])-1], ",") + if len(lens) > 1 { + len1, _ = strconv.ParseInt(lens[0], 10, 64) + len2, _ = strconv.ParseInt(lens[1], 10, 64) + } else { + len1, _ = strconv.ParseInt(lens[0], 10, 64) + } + } + if scale != 0 && precision != 0 { + col.Length = precision + col.Length2 = scale + } else { + col.Length = scale + } + if strings.Contains(dt, "SERIAL") { + col.IsAutoIncrement = true + } + + switch dt { + case "SERIAL8": + col.SQLType = schemas.SQLType{Name: schemas.BigSerial, DefaultLength: len1, DefaultLength2: len2} + case "VARCHAR2": + col.SQLType = schemas.SQLType{Name: schemas.Varchar, DefaultLength: len1, DefaultLength2: len2} + case "NVARCHAR2": + col.SQLType = schemas.SQLType{Name: schemas.NVarchar, DefaultLength: len1, DefaultLength2: len2} + case "TIMESTAMP WITH TIME ZONE": + col.SQLType = schemas.SQLType{Name: schemas.TimeStampz, DefaultLength: 0, DefaultLength2: 0} + case "DATETIME YEAR TO SECOND": + col.SQLType = schemas.SQLType{Name: schemas.TimeStamp, DefaultLength: 0, DefaultLength2: 0} + case "LONG", "LONG RAW": + col.SQLType = schemas.SQLType{Name: schemas.Text, DefaultLength: 0, DefaultLength2: 0} + case "RAW": + col.SQLType = schemas.SQLType{Name: schemas.Binary, DefaultLength: 0, DefaultLength2: 0} + case "ROWID": + col.SQLType = schemas.SQLType{Name: schemas.Varchar, DefaultLength: 18, DefaultLength2: 0} + case "SMALLFLOAT": + col.SQLType = schemas.SQLType{Name: schemas.Real, DefaultLength: 0, DefaultLength2: 0} + case "BYTE": + col.SQLType = schemas.SQLType{Name: schemas.Binary, DefaultLength: len1, DefaultLength2: len2} + case "AQ$_SUBSCRIBERS": + ignore = true + default: + if strings.Contains(dt, "DATETIME") && strings.Contains(dt, "FRACTION") { + col.SQLType = schemas.SQLType{Name: schemas.TimeStamp, DefaultLength: 0, DefaultLength2: 0} + } else { + col.SQLType = schemas.SQLType{Name: strings.ToUpper(dt), DefaultLength: len1, DefaultLength2: len2} + } + } + if ignore { + continue + } + if _, ok := schemas.SqlTypes[col.SQLType.Name]; !ok { + return nil, nil, fmt.Errorf("Unknown colType %v %v", *dataType, col.SQLType) + } + + cols[col.Name] = col + colSeq = append(colSeq, col.Name) + } + if rows.Err() != nil { + return nil, nil, rows.Err() + } + return colSeq, cols, nil +} + +func (db *gbase8s) ModifyColumnSQL(tableName string, col *schemas.Column) string { + s, _ := ColumnString(db.dialect, col, false, true) + modifyColumnSQL := fmt.Sprintf("ALTER TABLE %s MODIFY %s;", db.quoter.Quote(tableName), s) + if col.Comment != "" { + modifyColumnSQL += fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'", db.quoter.Quote(tableName), db.quoter.Quote(col.Name), col.Comment) + } + return modifyColumnSQL +} + +func (db *gbase8s) IsColumnExist(queryer core.Queryer, ctx context.Context, tableName, colName string) (bool, error) { + args := []interface{}{tableName, colName} + query := "SELECT colname FROM syscolumnsext c, systables t WHERE c.tabid = t.tabid and tabname = :1 AND colname = :2" + return db.HasRecords(queryer, ctx, query, args...) +} + +func (db *gbase8s) Filters() []Filter { + return []Filter{} +} + +type gbase8sDriver struct { + baseDriver +} + +func (g *gbase8sDriver) Features() *DriverFeatures { + return &DriverFeatures{ + SupportReturnInsertedID: true, + } +} + +func (g *gbase8sDriver) GenScanResult(colType string) (interface{}, error) { + colType = strings.Replace(colType, "SQLT_", "", 1) + switch colType { + case "CHAR", "NCHAR", "VARCHAR", "VARCHAR2", "NVARCHAR2", "AFC": + var s sql.NullString + return &s, nil + case "TEXT", "CLOB", "NCLOB", "CLOB2", "JSON", "XMLTYPE": + var s sql.NullString + return &s, nil + case "BYTE", "BLOB": + var r sql.RawBytes + return &r, nil + case "SERIAL", "SERIAL8", "BIGSERIAL", "BIGINT", "INT8": + var s sql.NullInt64 + return &s, nil + case "INTEGER", "INT", "SMALLINT": + var s sql.NullInt32 + return &s, nil + case "FLOAT", "SMALLFLOAT", "REAL": + var s sql.NullFloat64 + return &s, nil + case "DECIMAL", "NUMERIC", "MONEY", "NUMBER", "BDOUBLE": + var s sql.NullFloat64 + return &s, nil + case "DATE", "DATETIME", "TIMESTAMP", "DATETIME YEAR TO SECOND", "DATETIME YEAR TO FRACTION": + var s sql.NullTime + return &s, nil + case "INTERVAL": + var s sql.NullString + return &s, nil + case "BOOLEAN": + var s sql.NullBool + return &s, nil + default: + // Check whether it is a variant of DATETIME YEAR TO FRACTION + if strings.Contains(colType, "DATETIME") && strings.Contains(colType, "FRACTION") { + var s sql.NullTime + return &s, nil + } + var r sql.RawBytes + return &r, nil + } +} + +// dataSourceName=user/password@ipv4:port/dbname +// gbase8s://user:password@ip:port/dbname?param2=1¶m2=2 +func (o *gbase8sDriver) Parse(driverName, dataSourceName string) (*URI, error) { + db := &URI{DBType: schemas.GBASE8S} + dsnPattern := regexp.MustCompile( + `^(?P.*):(?P.*)@` + // user:password@ + `(?P.*)` + // ip:port + `\/(?P.*)`) // dbname + matches := dsnPattern.FindStringSubmatch(dataSourceName) + names := dsnPattern.SubexpNames() + for i, match := range matches { + if names[i] == "dbname" { + db.DBName = match + } + } + if db.DBName == "" && len(matches) != 0 { + return nil, errors.New("dbname is empty") + } + return db, nil +} + +func (db *gbase8s) primaryKeys(queryer core.Queryer, ctx context.Context, tableName string) ([]string, error) { + s := `SELECT col.colname AS column_name FROM systables t + JOIN sysconstraints con ON con.tabid = t.tabid + JOIN sysindexes idx ON con.idxname = idx.idxname + JOIN syscolumns col ON col.tabid = t.tabid AND col.colno IN ( + idx.part1, idx.part2, idx.part3, idx.part4, idx.part5, idx.part6, idx.part7, idx.part8, + idx.part9, idx.part10, idx.part11, idx.part12, idx.part13, idx.part14, idx.part15, idx.part16 + ) + WHERE t.tabname = :1 AND con.constrtype = 'P'` + s = compressStr(s) // Compress whitespace in the query string + rows, err := queryer.QueryContext(ctx, s, tableName) + if err != nil { + return nil, err + } + defer rows.Close() + priCols := make([]string, 0) + for rows.Next() { + var priCol string + err = rows.Scan(&priCol) + if err != nil { + return nil, err + } + priCols = append(priCols, priCol) + } + if rows.Err() != nil { + return nil, rows.Err() + } + return priCols, nil +} + +func inSlice(target string, list []string) bool { + for _, item := range list { + if item == target { + return true + } + } + return false +} + +// Replace all consecutive whitespace characters with a single space using regex +func compressStr(s string) string { + re := regexp.MustCompile(`\s+`) + return strings.TrimSpace(re.ReplaceAllString(s, " ")) +} diff --git a/dialects/mysql.go b/dialects/mysql.go index d11c728b..424807d4 100644 --- a/dialects/mysql.go +++ b/dialects/mysql.go @@ -402,6 +402,9 @@ func (db *mysql) AddColumnSQL(tableName string, col *schemas.Column) string { // ModifyColumnSQL returns a SQL to modify SQL func (db *mysql) ModifyColumnSQL(tableName string, col *schemas.Column) string { s, _ := ColumnString(db.dialect, col, false, true) + if col.IsAutoIncrement { + s += " " + db.AutoIncrStr() + } if col.Comment != "" { s += fmt.Sprintf(" COMMENT '%s'", col.Comment) } diff --git a/dialects/oracle.go b/dialects/oracle.go index 5f614b1a..0170bcae 100644 --- a/dialects/oracle.go +++ b/dialects/oracle.go @@ -684,6 +684,17 @@ func (db *oracle) IndexCheckSQL(tableName, idxName string) (string, []interface{ `WHERE TABLE_NAME = :1 AND INDEX_NAME = :2`, args } +func (db *oracle) DropIndexSQL(tableName string, index *schemas.Index) string { + quote := db.dialect.Quoter().Quote + var name string + if index.IsRegular { + name = index.XName(tableName) + } else { + name = index.Name + } + return fmt.Sprintf("DROP INDEX %v", quote(name)) +} + func (db *oracle) IsTableExist(queryer core.Queryer, ctx context.Context, tableName string) (bool, error) { return db.HasRecords(queryer, ctx, `SELECT table_name FROM user_tables WHERE table_name = :1`, tableName) } diff --git a/engine.go b/engine.go index bb7d2325..9a379f2d 100644 --- a/engine.go +++ b/engine.go @@ -798,6 +798,22 @@ func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w return err } } + } else if sess.engine.dialect.URI().DBType == schemas.GBASE8S { + stp.Name = strings.Replace(stp.Name, "SQLT_", "", 1) + if stp.IsTime() && len(s.String) == 20 { // "2025-06-10T07:55:31Z" + t, err := time.Parse(time.RFC3339, s.String) + if err != nil { + return fmt.Errorf("failed to parse time %s: %v", s.String, err) + } + r := t.Format("2006-01-02 15:04:05") + if _, err = io.WriteString(w, "'"+r+"'"); err != nil { + return err + } + } else { + if _, err = io.WriteString(w, "'"+strings.ReplaceAll(s.String, "'", "''")+"'"); err != nil { + return err + } + } } else { if _, err = io.WriteString(w, "'"+strings.ReplaceAll(s.String, "'", "''")+"'"); err != nil { return err diff --git a/go.mod b/go.mod index 9808ec08..18cf7a40 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( gitee.com/travelliu/dm v1.8.11192 github.com/go-sql-driver/mysql v1.7.0 github.com/goccy/go-json v0.8.1 - github.com/jackc/pgx/v4 v4.18.0 + github.com/jackc/pgx/v4 v4.18.2 github.com/json-iterator/go v1.1.12 github.com/lib/pq v1.10.7 github.com/mattn/go-sqlite3 v1.14.16 @@ -27,10 +27,10 @@ require ( github.com/golang/snappy v0.0.4 // indirect github.com/google/uuid v1.3.0 // indirect github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.14.0 // indirect + github.com/jackc/pgconn v1.14.3 // indirect github.com/jackc/pgio v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.3.2 // indirect + github.com/jackc/pgproto3/v2 v2.3.3 // indirect github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/pgtype v1.14.0 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect @@ -39,10 +39,10 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect - golang.org/x/crypto v0.12.0 // indirect + golang.org/x/crypto v0.20.0 // indirect golang.org/x/mod v0.8.0 // indirect - golang.org/x/sys v0.11.0 // indirect - golang.org/x/text v0.12.0 // indirect + golang.org/x/sys v0.17.0 // indirect + golang.org/x/text v0.14.0 // indirect golang.org/x/tools v0.6.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect lukechampine.com/uint128 v1.2.0 // indirect diff --git a/go.sum b/go.sum index 0d3e0804..6b98a5ea 100644 --- a/go.sum +++ b/go.sum @@ -59,8 +59,8 @@ github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsU github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= -github.com/jackc/pgconn v1.14.0 h1:vrbA9Ud87g6JdFWkHTJXppVce58qPIdP7N8y0Ml/A7Q= -github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E= +github.com/jackc/pgconn v1.14.3 h1:bVoTr12EGANZz66nZPkMInAV/KHD2TxH9npjXXgiB3w= +github.com/jackc/pgconn v1.14.3/go.mod h1:RZbme4uasqzybK2RK5c65VsHxoyaml09lx3tXOcO/VM= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= @@ -76,8 +76,8 @@ github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvW github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= -github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.3 h1:1HLSx5H+tXR9pW3in3zaztoEwQYRC9SQaYUHjTSUOag= +github.com/jackc/pgproto3/v2 v2.3.3/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= @@ -91,12 +91,11 @@ github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08 github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= -github.com/jackc/pgx/v4 v4.18.0 h1:Ltaa1ePvc7msFGALnCrqKJVEByu/qYh5jJBYcDtAno4= -github.com/jackc/pgx/v4 v4.18.0/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE= +github.com/jackc/pgx/v4 v4.18.2 h1:xVpYkNR5pk5bMCZGfClbO962UIqVABcAGt7ha1s/FeU= +github.com/jackc/pgx/v4 v4.18.2/go.mod h1:Ey4Oru5tH5sB6tV7hDmfWFahwF15Eb7DNXlRKx2CkVw= github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= @@ -158,21 +157,15 @@ github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6Mwd github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0= @@ -196,14 +189,11 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= -golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= +golang.org/x/crypto v0.20.0 h1:jmAMJJZXr5KiCw05dfYK9QnqaqKLYXijU23lsEdcQqg= +golang.org/x/crypto v0.20.0/go.mod h1:Xwo95rrVNIoSMx9wa1JroENMToLWn3RNVrTBpLHgZPQ= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -212,12 +202,9 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -232,25 +219,18 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= -golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -258,9 +238,7 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/statements/args.go b/internal/statements/args.go index 727d5977..ce11dba0 100644 --- a/internal/statements/args.go +++ b/internal/statements/args.go @@ -5,10 +5,23 @@ package statements import ( + "database/sql/driver" + "fmt" + "xorm.io/builder" "xorm.io/xorm/schemas" ) +type DateTimeString struct { + Layout string + Str string +} + +// Value implements the driver Valuer interface. +func (n DateTimeString) Value() (driver.Value, error) { + return n.Str, nil +} + // WriteArg writes an arg func (statement *Statement) WriteArg(w *builder.BytesWriter, arg interface{}) error { switch argv := arg.(type) { @@ -22,6 +35,17 @@ func (statement *Statement) WriteArg(w *builder.BytesWriter, arg interface{}) er if _, err := w.WriteString(")"); err != nil { return err } + case *DateTimeString: + if statement.dialect.URI().DBType == schemas.ORACLE { + if _, err := fmt.Fprintf(w, `TO_DATE(?,'%s')`, argv.Layout); err != nil { + return err + } + } else { + if err := w.WriteByte('?'); err != nil { + return err + } + } + w.Append(arg) default: if err := w.WriteByte('?'); err != nil { return err diff --git a/internal/statements/legacy_select.go b/internal/statements/legacy_select.go index c6fd86b7..23a749a7 100644 --- a/internal/statements/legacy_select.go +++ b/internal/statements/legacy_select.go @@ -19,8 +19,9 @@ func (statement *Statement) isUsingLegacyLimitOffset() bool { func (statement *Statement) writeMssqlLegacySelect(buf *builder.BytesWriter, columnStr string) error { return statement.writeMultiple(buf, statement.writeStrings("SELECT"), - statement.writeDistinct, statement.writeTop, + statement.writeDistinct, + statement.writeStrings(" ", columnStr), statement.writeFrom, statement.writeWhereWithMssqlPagination, statement.writeGroupBy, diff --git a/internal/statements/values.go b/internal/statements/values.go index 4c1360ed..f27caedb 100644 --- a/internal/statements/values.go +++ b/internal/statements/values.go @@ -88,7 +88,20 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl if fieldType.ConvertibleTo(schemas.TimeType) { t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time) tf, err := dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t) - return tf, err + if val, ok := tf.(string); ok { + var layout string + switch col.SQLType.Name { + case schemas.Date: + layout = "yyyy-MM-dd" + case schemas.Time: + layout = "HH24:mi:ss" + default: + layout = "yyyy-MM-dd HH24:mi:ss" + } + return &DateTimeString{Layout: layout, Str: val}, err + } else { + return tf, err + } } else if fieldType.ConvertibleTo(nullFloatType) { t := fieldValue.Convert(nullFloatType).Interface().(sql.NullFloat64) if !t.Valid { diff --git a/schemas/column.go b/schemas/column.go index 08d34b91..2da18ee5 100644 --- a/schemas/column.go +++ b/schemas/column.go @@ -26,6 +26,7 @@ type Column struct { FieldIndex []int // Available only when parsed from a struct SQLType SQLType IsJSON bool + IsJSONB bool Length int64 Length2 int64 Nullable bool diff --git a/schemas/type.go b/schemas/type.go index 3dbcee7e..aa0fce27 100644 --- a/schemas/type.go +++ b/schemas/type.go @@ -23,6 +23,7 @@ const ( MSSQL DBType = "mssql" ORACLE DBType = "oracle" DAMENG DBType = "dameng" + GBASE8S DBType = "gbase8s" ) // SQLType represents SQL types diff --git a/sync.go b/sync.go index 44167652..b8b827da 100644 --- a/sync.go +++ b/sync.go @@ -198,6 +198,7 @@ func (session *Session) SyncWithOptions(opts SyncOptions, beans ...interface{}) } } else if col.Comment != oriCol.Comment { if engine.dialect.URI().DBType == schemas.POSTGRES || + engine.dialect.URI().DBType == schemas.GBASE8S || engine.dialect.URI().DBType == schemas.MYSQL { _, err = session.exec(engine.dialect.ModifyColumnSQL(tbNameWithSchema, col)) } diff --git a/tags/parser.go b/tags/parser.go index 53ef0c10..8b97160e 100644 --- a/tags/parser.go +++ b/tags/parser.go @@ -250,10 +250,16 @@ func (parser *Parser) parseFieldWithTags(table *schemas.Table, fieldIndex int, f } if col.SQLType.Name == "" { - var err error - col.SQLType, err = parser.getSQLTypeByType(field.Type) - if err != nil { - return nil, err + if col.IsJSONB { // check is jsonb first because it is also json + col.SQLType = schemas.SQLType{Name: schemas.Jsonb} + } else if col.IsJSON { + col.SQLType = schemas.SQLType{Name: schemas.Json} + } else { + var err error + col.SQLType, err = parser.getSQLTypeByType(field.Type) + if err != nil { + return nil, err + } } } if ctx.isUnsigned && col.SQLType.IsNumeric() && !strings.HasPrefix(col.SQLType.Name, "UNSIGNED") { diff --git a/tags/parser_test.go b/tags/parser_test.go index 434cfc07..be6ea6fe 100644 --- a/tags/parser_test.go +++ b/tags/parser_test.go @@ -577,7 +577,7 @@ func TestParseWithJSONB(t *testing.T) { assert.EqualValues(t, "struct_with_jsonb", table.Name) assert.EqualValues(t, 1, len(table.Columns())) assert.EqualValues(t, "default1", table.Columns()[0].Name) - assert.True(t, table.Columns()[0].IsJSON) + assert.True(t, table.Columns()[0].IsJSONB) } func TestParseWithSQLType(t *testing.T) { @@ -617,3 +617,53 @@ func TestParseWithSQLType(t *testing.T) { assert.EqualValues(t, "DATETIME", table.Columns()[3].SQLType.Name) assert.EqualValues(t, "UUID", table.Columns()[4].SQLType.Name) } + +func TestParseWithJSONLongText(t *testing.T) { + parser := NewParser( + "db", + dialects.QueryDialect("mysql"), + names.GonicMapper{ + "JSON": true, + }, + names.GonicMapper{ + "JSON": true, + }, + caches.NewManager(), + ) + + type StructWithJSONLongText struct { + Col1 string `db:"LongText json"` + } + + table, err := parser.Parse(reflect.ValueOf(new(StructWithJSONLongText))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_json_long_text", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + assert.EqualValues(t, "col1", table.Columns()[0].Name) + assert.EqualValues(t, "LONGTEXT", table.Columns()[0].SQLType.Name) + assert.EqualValues(t, true, table.Columns()[0].IsJSON) + + type StructWithJSONLongText2 struct { + Col1 string `db:"json"` + } + + table, err = parser.Parse(reflect.ValueOf(new(StructWithJSONLongText2))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_json_long_text2", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + assert.EqualValues(t, "col1", table.Columns()[0].Name) + assert.EqualValues(t, "JSON", table.Columns()[0].SQLType.Name) + assert.EqualValues(t, true, table.Columns()[0].IsJSON) + + type StructWithJSONLongText3 struct { + Col1 string `db:"jsonb"` + } + + table, err = parser.Parse(reflect.ValueOf(new(StructWithJSONLongText3))) + assert.NoError(t, err) + assert.EqualValues(t, "struct_with_json_long_text3", table.Name) + assert.EqualValues(t, 1, len(table.Columns())) + assert.EqualValues(t, "col1", table.Columns()[0].Name) + assert.EqualValues(t, "JSONB", table.Columns()[0].SQLType.Name) + assert.EqualValues(t, true, table.Columns()[0].IsJSONB) +} diff --git a/tags/tag.go b/tags/tag.go index 55f0b7c7..cfe35c8b 100644 --- a/tags/tag.go +++ b/tags/tag.go @@ -124,10 +124,16 @@ var defaultTagHandlers = map[string]Handler{ "EXTENDS": ExtendsTagHandler, "UNSIGNED": UnsignedTagHandler, "COLLATE": CollateTagHandler, + "JSON": JSONTagHandler, + "JSONB": JSONBTagHandler, } func init() { for k := range schemas.SqlTypes { + // don't override default tag handlers + if _, ok := defaultTagHandlers[k]; ok { + continue + } defaultTagHandlers[k] = SQLTypeTagHandler } } @@ -293,12 +299,20 @@ func CollateTagHandler(ctx *Context) error { return nil } +func JSONTagHandler(ctx *Context) error { + ctx.col.IsJSON = true + return nil +} + +func JSONBTagHandler(ctx *Context) error { + ctx.col.IsJSONB = true + ctx.col.IsJSON = true // jsonb is also json + return nil +} + // SQLTypeTagHandler describes SQL Type tag handler func SQLTypeTagHandler(ctx *Context) error { ctx.col.SQLType = schemas.SQLType{Name: ctx.tagUname} - if ctx.tagUname == "JSON" || ctx.tagUname == "JSONB" { - ctx.col.IsJSON = true - } if len(ctx.params) == 0 { return nil } diff --git a/tests/schema_test.go b/tests/schema_test.go index 3657950e..f2487175 100644 --- a/tests/schema_test.go +++ b/tests/schema_test.go @@ -751,3 +751,72 @@ func getKeysFromMap(m map[string]*schemas.Index) []string { } return ss } + +type SyncTestUser struct { + Id int64 `xorm:"pk autoincr 'id' comment('primary key 1')"` + Name string `xorm:"'name' notnull comment('nickname')" json:"name"` +} + +func (m *SyncTestUser) TableName() string { + return "sync_test_user" +} + +type SyncTestUser2 struct { + Id int64 `xorm:"pk autoincr 'id' comment('primary key 2')"` + Name string `xorm:"'name' notnull comment('nickname')" json:"name"` +} + +func (m *SyncTestUser2) TableName() string { + return "sync_test_user" +} + +func TestSync2_3(t *testing.T) { + if testEngine.Dialect().URI().DBType == schemas.MYSQL { + assert.NoError(t, PrepareEngine()) + assertSync(t, new(SyncTestUser)) + + assert.NoError(t, testEngine.Sync2(new(SyncTestUser2))) + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + tableInfo, err := testEngine.TableInfo(new(SyncTestUser2)) + + assert.EqualValues(t, tables[0].GetColumn("id").IsAutoIncrement, tableInfo.GetColumn("id").IsAutoIncrement) + assert.EqualValues(t, tables[0].GetColumn("id").Name, tableInfo.GetColumn("id").Name) + assert.EqualValues(t, tables[0].GetColumn("id").SQLType.Name, tableInfo.GetColumn("id").SQLType.Name) + assert.EqualValues(t, tables[0].GetColumn("id").Nullable, tableInfo.GetColumn("id").Nullable) + assert.EqualValues(t, tables[0].GetColumn("id").Comment, tableInfo.GetColumn("id").Comment) + + } +} + +func TestSyncJSON(t *testing.T) { + type SyncTestJSON struct { + Id int64 + Value string `xorm:"LONGTEXT JSON 'value' comment('json value')"` + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(SyncTestJSON)) + + assert.NoError(t, testEngine.Sync(new(SyncTestJSON))) + tables, err := testEngine.DBMetas() + assert.NoError(t, err) + + tableInfo, err := testEngine.TableInfo(new(SyncTestJSON)) + assert.NoError(t, err) + + assert.EqualValues(t, tables[0].GetColumn("id").IsAutoIncrement, tableInfo.GetColumn("id").IsAutoIncrement) + assert.EqualValues(t, tables[0].GetColumn("id").Name, tableInfo.GetColumn("id").Name) + if testEngine.Dialect().URI().DBType == schemas.MYSQL { + assert.EqualValues(t, tables[0].GetColumn("id").SQLType.Name, tableInfo.GetColumn("id").SQLType.Name) + } + assert.EqualValues(t, tables[0].GetColumn("id").Nullable, tableInfo.GetColumn("id").Nullable) + + assert.EqualValues(t, tables[0].GetColumn("value").IsAutoIncrement, tableInfo.GetColumn("value").IsAutoIncrement) + assert.EqualValues(t, tables[0].GetColumn("value").Name, tableInfo.GetColumn("value").Name) + assert.EqualValues(t, tables[0].GetColumn("value").Nullable, tableInfo.GetColumn("value").Nullable) + + if testEngine.Dialect().URI().DBType == schemas.MYSQL { + assert.EqualValues(t, tables[0].GetColumn("value").SQLType.Name, tableInfo.GetColumn("value").SQLType.Name) + } +} diff --git a/tests/session_find_test.go b/tests/session_find_test.go index d991e6ba..33552999 100644 --- a/tests/session_find_test.go +++ b/tests/session_find_test.go @@ -899,6 +899,58 @@ func TestFindExtends3(t *testing.T) { assert.EqualValues(t, 2, len(results)) } +func TestFindExtends4(t *testing.T) { + type FindExtends4A struct { + Id int64 + Age int + Name string + } + + type FindExtends4B struct { + Id int64 + ExtId int64 `xorm:"index"` + Age int + Name string + Value int + } + + assert.NoError(t, PrepareEngine()) + assertSync(t, new(FindExtends4A), new(FindExtends4B)) + + fe := FindExtends4A{ + Age: 1, + Name: "1", + } + cnt, err := testEngine.Insert(&fe) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + cnt, err = testEngine.Insert(&FindExtends4B{ + ExtId: fe.Id, + Age: 2, + Name: "2", + Value: 3, + }) + assert.NoError(t, err) + assert.EqualValues(t, 1, cnt) + + type FindExtends4C struct { + FindExtends4A `xorm:"extends"` + FindExtends4B `xorm:"extends"` + } + var results []FindExtends4C + err = testEngine.Table("find_extends4_a"). + Join("INNER", "find_extends4_b", "`find_extends4_b`.`ext_id`=`find_extends4_a`.`id`"). + Find(&results) + assert.NoError(t, err) + assert.EqualValues(t, 1, len(results)) + assert.EqualValues(t, 1, results[0].FindExtends4A.Age) + assert.EqualValues(t, "1", results[0].FindExtends4A.Name) + assert.EqualValues(t, 2, results[0].FindExtends4B.Age) + assert.EqualValues(t, "2", results[0].FindExtends4B.Name) + assert.EqualValues(t, 3, results[0].FindExtends4B.Value) +} + func TestFindCacheLimit(t *testing.T) { type InviteCode struct { ID int64 `xorm:"pk autoincr 'id'"`