Compare commits

...

169 Commits
v1.2.0 ... v1

Author SHA1 Message Date
DacongDA 5ed154f8c5 Feat: support modify the varchar type column of postgresql database in sync function (#2414)
I'm not sure why the previous version did not support modifying the varchar length in Postgresql during sync. Perhaps it was considering compatibility issues?
As nobody reply me in #2408 I decide to make a pr to solve

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2414
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: DacongDA <dacongda1@sina.com>
Co-committed-by: DacongDA <dacongda1@sina.com>
2025-07-21 19:12:39 +00:00
maktub 8f5e437a61 fix: correct nullable field detection logic in column metadata (#2520)
fix: correct nullable field detection logic in column metadata

gbase8s metadata: N  == NOT NULL

Co-authored-by: zhangyunfei <zhangyunfei@gbase.cn>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2520
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: maktub <maktub@noreply.gitea.com>
Co-committed-by: maktub <maktub@noreply.gitea.com>
2025-07-21 18:24:41 +00:00
Lunny Xiao b72e98f60e Fix bug when json with a real SQLType in the tag (#2519)
Fix a bug when the tag have both `longtext json`. It should be `longtext` column type with JSON support.

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2519
2025-07-21 18:19:39 +00:00
maktub 89d1238248 feat: Support GBase8s Database (#2517)
## Background

Currently, XORM does not yet support the [GBase 8s database by Nanda General Data Technology]. GBase 8s is a widely used domestic database in China, commonly adopted by enterprises and government systems as a backend storage solution.

## Summary of This Contribution
	•	Added support for the gbase8s driver
	•	Implemented a basic database dialect: GBase8sDialect

## Usage Instructions

```go
import	_ "gitee.com/GBase8s/go-gci""

engine, err := xorm.NewEngine("gbase8s", "gbase8s://username:pwd@ip:port/dbname?param=xxx")

Co-authored-by: zhangyunfei <zhangyunfei@gbase.cn>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2517
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: maktub <maktub@noreply.gitea.com>
Co-committed-by: maktub <maktub@noreply.gitea.com>
2025-07-18 02:59:03 +00:00
Kos f50aacd38b fix oracle date field insert (#2420)
for #2419

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2420
Co-authored-by: Kos <kos@noreply.gitea.com>
Co-committed-by: Kos <kos@noreply.gitea.com>
2025-05-26 02:54:40 +00:00
Lunny Xiao 3b3f99f8a1 Add security scan (#2454)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2454
2025-03-14 17:04:18 +00:00
Lunny Xiao cc4be22330
Disable cockroach tempororily 2025-03-12 11:07:52 -07:00
Lunny Xiao cd0ca0bbc6
Update badges on README 2025-03-12 11:02:35 -07:00
cenxiao 844543c7da fix drop index SQL for Oracle (#2469)
Adjust drop index SQL to work with Oracle Autonomous DB

Issue:
DROP INDEX IDX_casdoor_permission_rule_PTYPE ON casdoor_permission_rule
Error report -
ORA-00933: SQL command not properly ended

Expected:
DROP INDEX IDX_casdoor_permission_rule_PTYPE

Co-authored-by: Cenxiao Zhao <cenxiao@projectboard.world>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2469
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: cenxiao <cenxiao@noreply.gitea.com>
Co-committed-by: cenxiao <cenxiao@noreply.gitea.com>
2025-02-28 06:26:20 +00:00
techknowlogick 7654b7b749 add libsql as a valid driver type (#2481)
support the libsql sqlite driver library https://github.com/tursodatabase/go-libsql

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2481
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: techknowlogick <techknowlogick@noreply.gitea.com>
Co-committed-by: techknowlogick <techknowlogick@noreply.gitea.com>
2024-12-06 17:26:39 +00:00
Lunny Xiao d47f35b260 Branch name change (#2459)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2459
2024-05-15 08:45:25 +00:00
Lunny Xiao cbbd1f09e1 Add more tests for extends (#2443)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2443
2024-05-09 12:47:01 +00:00
lijunshi c6d05fa553 fix: Sync2 will remove AUTO_INCREMENT unexpectly (#2444) (#2445)
fix #2444

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2445
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: lijunshi <lijunshi2015@163.com>
Co-committed-by: lijunshi <lijunshi2015@163.com>
2024-04-28 16:12:17 +00:00
Dacian S 3bc2ea24f6 suwro (#2453)
please release v1.3.10

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Dacian Stanciu <dacian.stanciu@just.ro>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2453
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Dacian S <suwro@noreply.gitea.com>
Co-committed-by: Dacian S <suwro@noreply.gitea.com>
2024-04-27 14:14:15 +00:00
Dacian Stanciu 4e74c80b67 fix: Legacy MsSQL - legacy offset select sql command bug #2446 (#2448)
fix for MsSQL legacy bug

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2448
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Dacian Stanciu <daci28@yahoo.com>
Co-committed-by: Dacian Stanciu <daci28@yahoo.com>
2024-04-24 13:47:05 +00:00
Lunny Xiao 8094e98a8f Fix ci (#2449)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2449
2024-04-24 11:37:00 +00:00
tylerthail2019 589acfff86 Add includeNil requiredField Check when custom struct field IsZero in Update method. (#2438)
Add includeNil requiredField Check when custom struct field IsZero in Update method.

Co-authored-by: tyler <tyler@mbp.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2438
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: tylerthail2019 <tylerthail2019@noreply.gitea.com>
Co-committed-by: tylerthail2019 <tylerthail2019@noreply.gitea.com>
2024-04-02 04:26:41 +00:00
tylerthail2019 34e62e9b4c add IsZero check for custom struct column in the Update method (#2417)
Co-authored-by: tyler <tyler@mbp.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2417
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: tylerthail2019 <tylerthail2019@noreply.gitea.com>
Co-committed-by: tylerthail2019 <tylerthail2019@noreply.gitea.com>
2024-03-18 06:43:33 +00:00
appleboy be77a7084b refactor: ensure non-null values in migration table creation (#2434)
- Change the migration table creation to not allow null values for the column

fix https://gitea.com/xorm/xorm/issues/2433

Signed-off-by: appleboy <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2434
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2024-03-17 03:53:16 +00:00
Lunny Xiao 7a535a7899 Update ci (#2432)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2432
2024-03-15 14:11:47 +00:00
Lunny Xiao 2e2a66c640 Support not drop index when sync (#2429)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2429
2024-03-15 03:13:09 +00:00
CyJaySong 63222312b2 Fix the issue of incorrect insertion of data in non UTC time zone zero for numeric types (#2413)
Fix the issue of incorrect insertion of data in non-UTC time zone zero for numeric types

Co-authored-by: CyJay <cyjay@MacBook-Pro.lan>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2413
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: CyJaySong <CyJaySong@gmail.com>
Co-committed-by: CyJaySong <CyJaySong@gmail.com>
2024-02-25 16:16:56 +00:00
Lunny Xiao 0c9a2f6a70 Fix migrate schema bug (#2407)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2407
2024-02-08 08:45:23 +00:00
Kos 11e96d9654 Update internal/statements/legacy_select.go (#2400)
fix #2399, When Oracle enables USE_LEGACY_LIMIT_OFFSET, the Where condition fails

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2400
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Kos <kos@noreply.gitea.com>
Co-committed-by: Kos <kos@noreply.gitea.com>
2024-02-05 11:13:17 +00:00
zzdboy 5301cc0ae9 fix: dm Database Clob field conversion error (#2405)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2405
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: zzdboy <28206697@qq.com>
Co-committed-by: zzdboy <28206697@qq.com>
2024-02-04 09:06:47 +00:00
James Marino e884f059a4 fix: allow driver options to allow the customisation of the driver after database open call (#2369)
Function can now be passed in to allow customisation to the open database.

For example, with the Oracle database driver `sijms/go-ora` one may want to do the following:

```go
engine, err := xorm.NewEngine("oracle", connectionString, func(db *sql.DB) error {
    return oracle.AddSessionParam(db, "NLS_TIMESTAMP_FORMAT", "RR-MM-DD HH24:MI:SS")
})
```

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2369
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: James Marino <james@marino.io>
Co-committed-by: James Marino <james@marino.io>
2024-01-22 08:24:18 +00:00
weeee9 ac3be4148e Fix typo in session_get.go (#2398)
- Fix a typo in error message: "unspoorted" should be "unsupported"

Signed-off-by: weeee9 <teletubby332@gmail.com>

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2398
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: weeee9 <teletubby332@gmail.com>
Co-committed-by: weeee9 <teletubby332@gmail.com>
2024-01-22 08:06:32 +00:00
Lunny Xiao 70c6978fc4 Use github.com/microsoft/go-mssqldb instead of old mssqldb repository (#2386) (#2390)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2386
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2390
2024-01-16 07:56:04 +00:00
Lunny Xiao 83fdc38834 Fix #2393 (#2395)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2395
2024-01-15 14:06:34 +00:00
Lunny Xiao a901ab8593
Fix readme links 2024-01-05 09:47:07 +08:00
Lunny Xiao 4992cba040 Fix test-mssql bug (#2391) (#2392)
backport #2391

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2391

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2392
2024-01-01 02:44:35 +00:00
Lunny Xiao 6ce46e7fac Fix mssql get indexes bug when collation is not default with collation test for mssql (#2387) (#2389)
backport #2387

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2387

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2389
2023-12-31 14:39:33 +00:00
Lunny Xiao 5d57ef92f5 Upgrade minimal version from go 1.16 to go 1.18 (#2388)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2388
2023-12-31 13:56:14 +00:00
Lunny Xiao 0398dee813 Implement update join (#2383) (#2385)
backport #2383

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2383

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2385
2023-12-30 10:50:02 +00:00
Lunny Xiao cc28d99161 Fix use hint with join bug (#2384)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2384
2023-12-30 06:28:44 +00:00
Lunny Xiao 743f3bcab8 Upgrade to setup-go v5 (#2381)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2381
2023-12-23 02:51:33 +00:00
Lunny Xiao bdd8787d84 Use setup go v4 actions (#2380)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2380
2023-12-20 07:01:32 +00:00
Lunny Xiao 7ae7474bcb Add missing index hint parameter (#2378)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2378
2023-12-19 04:54:04 +00:00
Lunny Xiao b571d91858 Add index hint support (#2375)
Fix #1456

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2375
2023-12-15 02:17:13 +00:00
KN4CK3R 529a264d8e Fix sqlite multiline query parse (#2367)
The app "DB Browser for SQLite" produces multiline queries in the `sqlite_master` table which xorm can't parse.

Examples:
```
CREATE TABLE "push_mirror"
(
    id             INTEGER           not null
        primary key autoincrement,
    repo_id        INTEGER,
    remote_name    TEXT,
    interval       INTEGER,
    created_unix   INTEGER,
    last_update    INTEGER,
    last_error     TEXT,
    sync_on_commit INTEGER default 0 not null
, `remote_address` TEXT NULL)
```
```
CREATE INDEX "IDX_audit_event_action" ON "audit_event" (
	"action"
)
```

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2367
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-committed-by: KN4CK3R <admin@oldschoolhack.me>
2023-12-01 07:07:35 +00:00
Lunny Xiao c855ca4e59 Some refactors (#2348) (#2352)
backport #2348

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2348

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2352
2023-10-27 14:01:14 +00:00
Lunny Xiao 6ef0a7798f Fix bug when join with alias start with `a` (#2343)
Fix #2331

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2343
2023-10-25 11:01:46 +00:00
Lunny Xiao 0f085408af some refactors for write functions (#2342)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2342
2023-10-25 07:11:18 +00:00
lng2020 dbe499091a Revert "Fix deleted tag attribute zeroTime is not DatabaseTZ (#2299)" (#2341)
Related #2339

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2341
Co-authored-by: lng2020 <nanguanlin6@gmail.com>
Co-committed-by: lng2020 <nanguanlin6@gmail.com>
2023-10-17 09:41:42 +00:00
zzdboy 3eda0f7805 fix KingbaseES version (#2335)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2335
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: zzdboy <28206697@qq.com>
Co-committed-by: zzdboy <28206697@qq.com>
2023-09-24 03:52:50 +00:00
FlyingOnion 551de3767c modify limit offset implement (#2188)
Oracle and SQLServer specific: When `LIMIT OFFSET` function is needed, use `OFFSET <offset> ROWS FETCH NEXT <limit> ROWS ONLY` to replace legacy subquery.

SQLServer specific: When `ORDER BY` is not set and `OFFSET FETCH` is set, set `statement.orderStr` to `1` (`ORDER BY 1`). See [here](https://learn.microsoft.com/zh-cn/sql/t-sql/queries/select-order-by-clause-transact-sql?view=sql-server-ver16).

MySQL specific: When limit is 0 and offset > 0, use `LIMIT 9223372036854775807` ($2^{63}-1$). See comments [here](15d171ea55/internal/statements/query.go (L314)).

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2188
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: FlyingOnion <731677080@qq.com>
Co-committed-by: FlyingOnion <731677080@qq.com>
2023-09-20 02:07:03 +00:00
Ryan Liu ac88a5705a fix the error in mysql: unknown colType UNSIGNED FLOAT (#2143)
Error: unknown colType UNSIGNED FLOAT
in mysql 5.6.27 or 5.7.32

Fix #2123

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2143
Co-authored-by: Ryan Liu <azhai@126.com>
Co-committed-by: Ryan Liu <azhai@126.com>
2023-09-16 15:43:12 +00:00
6543 e5be0f4129 Remove dead code from session.SyncWithOptions() (#2323)
db7c264062/sync.go (L229-L231)

as oriIndex only is **not** nil if index.Equal(index2)

and index.Equal(index2) check if `oriIndex.Type == index.Type` ... so it always is false

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2323
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
2023-09-16 14:41:02 +00:00
zzdboy 2885c88b77 fix PostgreSQL version (#2332)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2332
Co-authored-by: zzdboy <28206697@qq.com>
Co-committed-by: zzdboy <28206697@qq.com>
2023-09-16 13:49:19 +00:00
Lunny Xiao 407375c9b4 Add test for max ( id ) (#2316)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2316
2023-09-16 13:48:49 +00:00
Lunny Xiao eeacd22674 Fix ci (#2330)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2330
2023-09-13 02:02:12 +00:00
6543 db7c264062 Add Sync options to ignore constrains and indices (#2320)
needed for https://github.com/woodpecker-ci/woodpecker/pull/2117

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2320
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: 6543 <6543@obermui.de>
Co-committed-by: 6543 <6543@obermui.de>
2023-08-09 03:28:52 +00:00
Lunny Xiao ac84217e14 Exec support conversion data (#1970)
Fix #1803

Reviewed-on: https://gitea.com/xorm/xorm/pulls/1970
2023-08-07 10:54:57 +00:00
CyJaySong 94882e39df Fix deleted tag attribute zeroTime is not DatabaseTZ (#2299)
Co-authored-by: CyJay <cyjay@MacBook-Pro.lan>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2299
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: CyJaySong <CyJaySong@gmail.com>
Co-committed-by: CyJaySong <CyJaySong@gmail.com>
2023-08-07 04:28:55 +00:00
Lunny Xiao c622cdaf89
Add ignore 2023-07-26 18:46:36 +08:00
Lunny Xiao e2af77de3b
remove unused files 2023-07-26 18:45:28 +08:00
Lunny Xiao ed1a37b1c5 Move integrations -> tests (#2314)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2314
2023-07-26 10:44:41 +00:00
Lunny Xiao 47003ff937 Delete Limit with start will return error because it's not supported by database (#1895)
Fix #1894

Reviewed-on: https://gitea.com/xorm/xorm/pulls/1895
2023-07-26 06:58:55 +00:00
Lunny Xiao 12ec038853 Add test for get customized types (#2194)
Fix #2097

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2194
2023-07-26 03:03:01 +00:00
takumin 1572367155 Add dialects/time_test.go (#2169)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2169
Co-authored-by: takumin <takumiiinn@gmail.com>
Co-committed-by: takumin <takumiiinn@gmail.com>
2023-07-26 00:57:40 +00:00
Lunny Xiao 59b727260d Check orderby validate (#2313)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2313
2023-07-25 14:02:38 +00:00
Lunny Xiao cb4f310151 Refactor write update (#2310)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2310
2023-07-25 10:49:55 +00:00
Lunny Xiao 9aab1f689c Count will ignore order by as before (#2307)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2307
2023-07-25 09:27:25 +00:00
Lunny Xiao 4109ce1e23 Fix a serious bug when using rows and reuse the session (#2309)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2309
2023-07-25 08:37:12 +00:00
Lunny Xiao a13564976c refactor write update sql (#2304)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2304
2023-07-24 07:57:05 +00:00
arturwwl 24a672be3c convert - String2Time accept HH:mm:ss format (#2074)
resolves #2073

Co-authored-by: arturwwl <>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2074
Co-authored-by: arturwwl <arturwwl@noreply.gitea.com>
Co-committed-by: arturwwl <arturwwl@noreply.gitea.com>
2023-07-23 02:34:10 +00:00
Lunny Xiao 3626de1459 mod tidy (#2303)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2303
2023-07-23 01:31:59 +00:00
datbeohbbh c0d09c0def update go version in `go.mod` (#2224)
Hi! I would like to request an update to the `go.mod` file.

Co-authored-by: datbeohbbh <phongtomfapp@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2224
Co-authored-by: datbeohbbh <datbeohbbh@noreply.gitea.com>
Co-committed-by: datbeohbbh <datbeohbbh@noreply.gitea.com>
2023-07-23 01:30:49 +00:00
Lunny Xiao 6c29ab378e refactor write insert sql (#2302)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2302
2023-07-22 15:24:19 +00:00
Lunny Xiao 9988dac44d improve write order by (#2301)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2301
2023-07-22 11:52:38 +00:00
Lunny Xiao 96ed5584e3 move sql geneartion for update to statement (#2300)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2300
2023-07-21 16:25:58 +00:00
Lunny Xiao dabcb4c0ee Return error if count returned no row (#2298)
Fix #2139

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2298
2023-07-20 14:48:29 +00:00
Lunny Xiao 2df56f033a Some refactors (#2293)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2293
2023-07-20 14:45:31 +00:00
LinkinStars 9b71cb49cc The loadTableInfo function supports passing the context. (#2297)
## `loadTableInfo` add context parameter

### Main change
```diff
+++ func (engine *Engine) loadTableInfo(ctx context.Context, table *schemas.Table) error
--- func (engine *Engine) loadTableInfo(table *schemas.Table) error
```

### Situation
After #2200, I built custom dialect to control the SQL. I find that everything else is fine, except when the `SYNC` method executes with an exception.
The reason is that the `loadTableInfo` method calls the `GetIndexes` and `GetColumns` methods with the dialect during execution. **The context passed to these two methods are all `engine.defaultContext` not the current session's context.** So, I think the `loadTableInfo` method should add the context parameter to ensure that the correct context is used during execution.

### Review Note
1. dialect's `GetColumns` and `GetIndexes` methods are **only** used here, if the context here is incorrect, then the context parameter is invalid.
2. `loadTableInfo` method is only used in `SYNC` and `DBMetas` methods, `SYNC` should pass the session's context, while `DBMetas` has no problem passing `engine.defaultContext`.

All in all, I think this change should not affect other function.

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2297
Co-authored-by: LinkinStars <linkinstar@foxmail.com>
Co-committed-by: LinkinStars <linkinstar@foxmail.com>
2023-07-14 07:35:35 +00:00
CyJaySong 0b18440d73 fix time parse layout (#2296)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2296
Co-authored-by: CyJaySong <cyjaysong@gmail.com>
Co-committed-by: CyJaySong <cyjaysong@gmail.com>
2023-07-14 07:34:02 +00:00
Lunny Xiao f33221df74 Fix question mark replacement on postgres (#2202)
Fix #2112

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2202
2023-07-12 13:22:32 +00:00
takumin 722f1cc141 Generate coverage html (#2170)
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2170
Co-authored-by: takumin <takumiiinn@gmail.com>
Co-committed-by: takumin <takumiiinn@gmail.com>
2023-07-12 13:01:34 +00:00
Lunny Xiao 3b53a5f847 upgrade dependencies (#2223)
Fix #2209

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2223
2023-07-12 11:06:39 +00:00
洪坤安 73eee961cc Performance Optimization: reduce slice2Bean calling times of strings.ToLower and map creation times (#2255)
1. 优化性能:减少func (session *Session) slice2Bean方法中的strings.ToLower调用次数以及减少map创建和访问次数(见工单 https://gitea.com/xorm/xorm/issues/2243)

2. 新增SetDefaultJSONHandler方法,用于用户自行设置DefaultJSONHandler(见工单 https://gitea.com/xorm/xorm/issues/2129)

优化前:
加载耗时=16.079s,总记录数=4028940
加载耗时=15.775s,总记录数=4028940
加载耗时=15.946s,总记录数=4028940

优化后:
加载耗时=10.863s,总记录数=4028940
加载耗时=11.257s,总记录数=4028940
加载耗时=11.155s,总记录数=4028940

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2255
Co-authored-by: 洪坤安 <wuzili1234@163.com>
Co-committed-by: 洪坤安 <wuzili1234@163.com>
2023-07-12 09:53:19 +00:00
martinvigg 55d9407390 mysql: add CHAIN, RANK to reserved word list (#2282)
Reference: https://dev.mysql.com/doc/refman/8.0/en/keywords.html
Co-authored-by: Martin Viggiano <martin.viggiano@stonebranch.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2282
Co-authored-by: martinvigg <martinvigg@noreply.gitea.com>
Co-committed-by: martinvigg <martinvigg@noreply.gitea.com>
2023-07-12 08:52:23 +00:00
Lunny Xiao f1f5e7cd1a Some refactor (#2292)
replace #2285

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2292
2023-07-12 07:53:25 +00:00
Lunny Xiao 79a8bc804b Fix join problem (#2291)
Fix #2284

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2291
2023-07-12 02:01:56 +00:00
brookechen 486c344ba3 In SQLite3, Sync doesn't support Modify Column:Error: near MODIFY: syntax error (#2267)
rebase

Co-authored-by: brookechen <brookechen@noreply.gitea.io>
Co-authored-by: brookechen <brookechen@tencent.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2267
Co-authored-by: brookechen <brookechen@noreply.gitea.com>
Co-committed-by: brookechen <brookechen@noreply.gitea.com>
2023-07-11 17:10:36 +00:00
flyingpigge 52b01ce67f chore: ignore unnecessary char type cast in GetColumns (#2278)
PostgreSQL有两种char类型:`character`和`"char"`: https://www.postgresql.org/docs/current/datatype-character.html.

`pg_class`里的`relkind`是`"char"`类型,所以`GetColumns`这里应该转成`::"char"`或者和本PR里请求的一样去掉转换。这样对于PostgreSQL以及其他兼容PostgreSQL的数据库容错性更好,在做比较时它们通常都会被隐式转换。
```sql
postgres=# select pg_typeof(relkind), pg_typeof('a'::char), pg_typeof('a'::"char") from pg_class limit 1;
 pg_typeof | pg_typeof | pg_typeof
-----------+-----------+-----------
 "char"    | character | "char"
(1 row)
```

Co-authored-by: August <pin@singularity-data.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2278
Co-authored-by: flyingpigge <flyingpigge@noreply.gitea.com>
Co-committed-by: flyingpigge <flyingpigge@noreply.gitea.com>
2023-07-03 09:15:45 +00:00
Lunny Xiao d29fe49933 Mysql support a new tag Collate (#2283)
Fix #237
Fix #2179

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2283
2023-07-01 03:40:09 +00:00
Lunny Xiao 18f8e7a86c Default don't log warn for database extra columns when syncing (#2280)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2280
2023-06-23 15:00:31 +00:00
Lunny Xiao 068de8c0f8 Don't warn when database have extra columns which are not in struct (#2279)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2279
2023-06-23 08:48:37 +00:00
ccbhj 838a0d9bca ccbhj/fix_mysql_blob_shared_bytes (#2274)
Copy the sql.RawBytes when converting to []byte.
Fix issue https://gitea.com/xorm/xorm/issues/2273

Co-authored-by: Bingjia Chen <bingjia.chen@seamoney.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2274
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: ccbhj <ccbhj@noreply.gitea.com>
Co-committed-by: ccbhj <ccbhj@noreply.gitea.com>
2023-06-12 09:18:13 +00:00
Lunny Xiao caa8a029c6 some optimzation (#2272)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2272
2023-06-02 14:16:30 +00:00
LinkinStars cb851a2f95 Filter support passing context (#2200) (#2270)
```diff
// Filter is an interface to filter SQL
type Filter interface {
---	Do(sql string) string
+++	Do(ctx context.Context, sql string) string
}
```

### Adds a `Context` parameter to the `Do` method of the Filter interface.

Developers can rewrite SQL through the `Filter` `Do` method and **need to get the necessary data from the Context** to assist.

For example, get user information through `Context`, so that different users can use different tables. Another example is to get the flags through `Context` to add annotations to the SQL, and use the annotations to let the subsequent `DB-Proxy` to achieve read/write separation.

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2270
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: LinkinStars <linkinstar@foxmail.com>
Co-committed-by: LinkinStars <linkinstar@foxmail.com>
2023-05-31 01:43:24 +00:00
Zettat123 57f7d69f1b Fix test-cockroach workflow (#2269)
The image has already specified the entrypoint so we don't need to call `/cockroach/cockroach` in `cmd`.
![image](/attachments/669496c1-5673-4609-abc7-f42846e4d479)

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2269
Co-authored-by: Zettat123 <zettat123@gmail.com>
Co-committed-by: Zettat123 <zettat123@gmail.com>
2023-05-29 13:01:43 +00:00
Lunny Xiao 04d36cfa81 Use actions instead of drone (#2258)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2258
2023-05-25 18:15:09 +08:00
brookechen 190384b4cd AutoIncrement列带ID插入数据时没有Commit (#2264)
业务场景中,需要预留(1 ~ 100)的ID给系统规则使用。所以会先使用插入将AutoIncrement列的id偏移到一个特定的值(如:100),然后“带ID调用Insert插入系统规则”。 当带ID插入时,由于没有commit,会被rollback掉。

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2264
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: brookechen <brookechen@noreply.gitea.io>
Co-committed-by: brookechen <brookechen@noreply.gitea.io>
2023-05-17 15:20:40 +08:00
Martin Viggiano e6907e9a62 oracle: Fix quotes on DropTableSQL function, add IsSequenceExist function. (#2265)
- Fixed quotes on drop table SQL statement.
- Implemented IsSequenceExists function for oracle dialect.

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2265
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Martin Viggiano <martinvigg@gmail.com>
Co-committed-by: Martin Viggiano <martinvigg@gmail.com>
2023-05-17 09:04:00 +08:00
Abei1uo 23be940bad Fix session insert interface slice commit panic(#2259) (#2260)
Fix session insert interface slice commit panic(#2259)
-- TestInsertMulti2InterfaceTransaction is the test case

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2260
Co-authored-by: Abei1uo <2676567028@qq.com>
Co-committed-by: Abei1uo <2676567028@qq.com>
2023-05-12 14:58:38 +08:00
Alberto Garcia 94fcec7f65 parse timestamp with milliseconds in tz format (#2246)
This pr fixes a bug caused when a timestmap in TZ format with milliseconds (`2023-04-05T15:50:48.256816Z` or `2023-04-05T15:50:48.256816+08:00`) is being parsed.

- The bug is happens in the function `String2Time` in `convert/time.go`
- if the timestamp contains milliseconds a layout with the structure `2006-01-02 15:04:05.` is used to parse it
- This layout contains an space between the date and the hour, if the timestamp returned by the db contains a `T` the parser fails and an error is returned

This pr adds a check for the `T` and milliseconds in the timestamp, if both conditions are present the date is parsed using the RFC3339 layout

Added test cases to check timestamps in this format

Solves [#2244](https://gitea.com/xorm/xorm/issues/2244)

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2246
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Alberto Garcia <agarcia@circutor.com>
Co-committed-by: Alberto Garcia <agarcia@circutor.com>
2023-04-06 21:47:59 +08:00
datbeohbbh d485abba57 add `(*Engine) SetConnMaxIdleTime` (#2229)
issue: https://gitea.com/xorm/xorm/issues/2228
Co-authored-by: datbeohbbh <phongtomfapp@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2229
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: datbeohbbh <datbeohbbh@noreply.gitea.io>
Co-committed-by: datbeohbbh <datbeohbbh@noreply.gitea.io>
2023-03-06 18:55:33 +08:00
fanshengshuai 914f2db9ea mysql字段为UNSIGNED时,会导致字段类型无法识别,返回RawBytes,JSON 字段映射为String (#2225)
如题

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2225
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: fanshengshuai <fanshengshuai@noreply.gitea.io>
Co-committed-by: fanshengshuai <fanshengshuai@noreply.gitea.io>
2023-02-28 23:42:42 +08:00
KN4CK3R 056cecc97e Add `Truncate` method (#2220)
This PR adds a `Truncate` method which allows to delete all existing rows in a table. The current `Delete` implementation enforces conditions to prevent accidental data deletion.

Co-authored-by: KN4CK3R <admin@oldschoolhack.me>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2220
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: KN4CK3R <kn4ck3r@noreply.gitea.io>
Co-committed-by: KN4CK3R <kn4ck3r@noreply.gitea.io>
2023-02-20 07:17:35 +08:00
datbeohbbh 52855dae32 update go version to v1.17 in .drone.yml (#2219)
issue: #2218
Co-authored-by: datbeohbbh <phongtomfapp@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2219
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: datbeohbbh <datbeohbbh@noreply.gitea.io>
Co-committed-by: datbeohbbh <datbeohbbh@noreply.gitea.io>
2023-02-12 11:16:53 +08:00
jamlacey 0c9963c637 Add support for go-ora driver (#2215)
Co-authored-by: James Lacey <jamlacey@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2215
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: jamlacey <jamlacey@noreply.gitea.io>
Co-committed-by: jamlacey <jamlacey@noreply.gitea.io>
2023-02-04 21:24:29 +08:00
datbeohbbh 7dc2a18876 Bug fix: `Rows` must be closed after used (#2214)
Issue: [#2213](https://gitea.com/xorm/xorm/issues/2213#issue-132724)
Co-authored-by: datbeohbbh <phongtomfapp@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2214
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: datbeohbbh <datbeohbbh@noreply.gitea.io>
Co-committed-by: datbeohbbh <datbeohbbh@noreply.gitea.io>
2023-02-03 17:24:16 +08:00
tamalsaha 5fafa00043 fix: Correctly parse jsonb column tag (#2206)
Signed-off-by: Tamal Saha <tamal@appscode.com>
Co-authored-by: Tamal Saha <tamal@appscode.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2206
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: tamalsaha <tamalsaha@noreply.gitea.io>
Co-committed-by: tamalsaha <tamalsaha@noreply.gitea.io>
2023-01-09 13:19:29 +08:00
Dmitry Narizhnykh 71270edfcc add delimiters between COMMENT ON COLUMN... for Postgres (#2156)
Fix bug: CreateTableSQL func generates invalid SQL without ";" delimiters.

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2156
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Dmitry Narizhnykh <slotix@noreply.gitea.io>
Co-committed-by: Dmitry Narizhnykh <slotix@noreply.gitea.io>
2022-12-12 18:35:40 +08:00
Lunny Xiao f1bfc5ce98 join support condition (#2201)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2201
2022-12-09 23:37:26 +08:00
tylerthail2019 71a5939c65 add disable version check func (#2197)
Co-authored-by: tyler <tylerthail@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2197
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: tylerthail2019 <tylerthail2019@noreply.gitea.io>
Co-committed-by: tylerthail2019 <tylerthail2019@noreply.gitea.io>
2022-11-16 13:22:04 +08:00
stevefan1999 3acabdaf26 Fix Oracle Table creation default value (#2190)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2190
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: stevefan1999 <stevefan1999@noreply.gitea.io>
Co-committed-by: stevefan1999 <stevefan1999@noreply.gitea.io>
2022-10-24 11:29:54 +08:00
Lunny Xiao bd58520020
add changelog for 1.3.2 2022-09-03 15:14:19 +08:00
Andrew Thornton c900ecc87f Prevent Sync failure with non-regular indexes on Postgres (#2174)
When dropping indexes in Postgres if the index is non-regular we
should not attempt to regularise the index name as it is already correct.

Signed-off-by: Andrew Thornton <art27@cantab.net>

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2174
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-committed-by: Andrew Thornton <art27@cantab.net>
2022-09-03 10:12:17 +08:00
Andrew Thornton c3bce55620 Change schemas.Column to use int64 (#2160)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2160
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-committed-by: Andrew Thornton <art27@cantab.net>
2022-07-14 13:55:24 +08:00
Lunny Xiao f469d88166
Update changelog for 1.3.1 2022-06-03 17:44:49 +08:00
Lunny Xiao c98930f8f2 Fix oid index for postgres (#2154)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2154
2022-06-03 15:24:24 +08:00
Lunny Xiao f9a6990ecb Refactor orderby and support arguments (#2150)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2150
2022-05-31 11:00:28 +08:00
Andrew Thornton eeb7fcf22c Add ORDER BY SEQ_IN_INDEX to MySQL GetIndexes to Fix IndexTests (#2152)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2152
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-committed-by: Andrew Thornton <art27@cantab.net>
2022-05-30 18:36:23 +08:00
Andrew Thornton 60540cbabe Remove const insertSelectPlaceholder and associated dead code (#2151)
`insertSelectPlaceholder` is an unexported const set at true. No code changes this nor can any build environment change it.

Therefore we should remove it and the associated dead code.

Close #2146

Signed-off-by: Andrew Thornton <art27@cantab.net>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2151
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-committed-by: Andrew Thornton <art27@cantab.net>
2022-05-30 00:29:03 +08:00
Andrew Thornton 26d291bbc3 Add interface to allow structs to provide specific index information (#2137)
The current mechanism for adding information about indices cannot express the ordering
of the columns in the index or add sorting information.

Here we add a new interface TableIndices which a struct would implement to provide
a slice of *schema.Index to provide additional indices to that gleaned from the tags.

Signed-off-by: Andrew Thornton <art27@cantab.net>

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2137
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-committed-by: Andrew Thornton <art27@cantab.net>
2022-04-24 19:34:27 +08:00
Lunny Xiao f7e9fb74ac return a clear error for set TEXT type as compare condition (#2062)
Fix #523

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2062
2022-04-23 17:19:37 +08:00
Lunny Xiao 2c064b6da6 Add test for find date (#2121)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2121
2022-04-22 14:56:26 +08:00
Pierre-Louis Bonicoli e1d4365667 MySQL/MariaDB: return max length for text columns (#2133)
MySQL/MariaDB: return max length for text columns using `CHARACTER_MAXIMUM_LENGTH`.

 Tests:
 * add an integration test: `TestGetColumnsLength`
 * update `TestSyncTable3` since `TableInfo` isn't able to provide the column size

Co-authored-by: Pierre-Louis Bonicoli <pierre-louis.bonicoli@libregerbil.fr>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2133
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Pierre-Louis Bonicoli <pilou@noreply.gitea.io>
Co-committed-by: Pierre-Louis Bonicoli <pilou@noreply.gitea.io>
2022-04-22 10:48:53 +08:00
Lunny Xiao 8f2596bf64 some improvement (#2136)
Fix #2134 and replace #2135

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2136
2022-04-22 10:16:35 +08:00
Pierre-Louis Bonicoli ea9bba0d14 PostgreSQL: enable comment on column (#2131)
The [oldest unsupported version documentation](https://www.postgresql.org/docs/7.1/sql-comment.html) states that comment on a column is supported.

Update `TestGetColumnsComment` in order to check both MySQL/MariaDB and PostgreSQL.

Co-authored-by: Pierre-Louis Bonicoli <pierre-louis.bonicoli@libregerbil.fr>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2131
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Pierre-Louis Bonicoli <pilou@noreply.gitea.io>
Co-committed-by: Pierre-Louis Bonicoli <pilou@noreply.gitea.io>
2022-04-17 18:03:29 +08:00
Lunny Xiao e858b75756
Update changelog for 1.3.0 2022-04-14 10:12:39 +08:00
getsu b3f9c53d8a oracle数据库不再拼接AS (#2109)
修复 #2108

Co-authored-by: chendy <acrhwfy@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2109
Co-authored-by: getsu <getsu@noreply.gitea.io>
Co-committed-by: getsu <getsu@noreply.gitea.io>
2022-03-31 23:57:40 +08:00
finelog d195040cb9 fix session context overwrite when logSessionId not set (#2115)
ref pr https://gitea.com/xorm/xorm/pulls/2053

i think the previous fix has some issue
for example, i'm using session like this:

```go
// logSessionID == false
engine := NewEngine()

// use ctx.SessionId to distinguish uniq request id
cxt := context.WithValue(parent, log.SessionIDKey, "some unique request id")
session := engine.NewSession().Context(ctx)
```
however, with pr 2053, `session.Context` can't get SessionId from ctx.

this pr fix abrove issue, overwrite `session.Context()` only when `engine.logSessionID  == true`

please check it out,thanks!

Co-authored-by: finelog <kaicltw@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2115
Co-authored-by: finelog <finelog@noreply.gitea.io>
Co-committed-by: finelog <finelog@noreply.gitea.io>
2022-03-31 17:20:29 +08:00
Pierre-Louis Bonicoli 79a21b68aa replace GitHub links: xorm has been moved to gitea.com (#2126)
Co-authored-by: Pierre-Louis Bonicoli <pierre-louis.bonicoli@libregerbil.fr>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2126
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Pierre-Louis Bonicoli <pilou@noreply.gitea.io>
Co-committed-by: Pierre-Louis Bonicoli <pilou@noreply.gitea.io>
2022-03-31 14:26:05 +08:00
fuge 3180c418c2 bugfix :Oid It's a special index. You can't put it in (#2105)
Co-authored-by: fuge <8342337@qq.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2105
Co-authored-by: fuge <fuge@noreply.gitea.io>
Co-committed-by: fuge <fuge@noreply.gitea.io>
2022-01-25 13:28:46 +08:00
finelog 7802393d01 fix reset colmap when counting distinct cols (#2096)
when using distinct cols with FindAndCount, reset statement.ColumnMap will make the counting sql an syntax error, this pr try fix this.
is this pr ok for merge?

error sql logging:
```sql
[SQL] SELECT DISTINCT `Fid` FROM `table_demo` WHERE Fkey = ? [val]

[SQL] SELECT count(DISTINCT ) FROM `table_demo` WHERE Fkey = ? [val]
```

after fix:
```sql
[SQL] SELECT DISTINCT `Fid` FROM `table_demo` WHERE Fkey = ? [val]

[SQL] SELECT count(DISTINCT `Fid`) FROM `table_demo` WHERE Fkey = ? [val]
```

Co-authored-by: finelog <kaicltw@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2096
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: finelog <finelog@noreply.gitea.io>
Co-committed-by: finelog <finelog@noreply.gitea.io>
2022-01-25 11:09:41 +08:00
appleboy e4e830bc78 chore(lint): remove revive and misspell command from makefile (#2088)
replace revive and misspell with golangci lint

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2088
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2022-01-16 19:04:15 +08:00
fuge d13b607d75 oracle分页,start 为 0 的bug (#2098)
删除前面判断即可。

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2098
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: fuge <fuge@noreply.gitea.io>
Co-committed-by: fuge <fuge@noreply.gitea.io>
2022-01-16 18:04:24 +08:00
jixianlqb 3d1c9fb761 对 DATE格式进行转换 (#2093)
对 DATE格式进行转换

Co-authored-by: laiqiangbin <laiqiangbin@chandashi.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2093
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: jixianlqb <jixianlqb@noreply.gitea.io>
Co-committed-by: jixianlqb <jixianlqb@noreply.gitea.io>
2022-01-16 16:36:07 +08:00
Andrew Thornton cd36b112ae Escape string and blob results from dump more correctly (#2091)
dumpTables currently badly handles BLOB and TEXT data containing control
characters:

* MySQL will interpret and unescape string literals e.g.`\r` will become
carriage return.

* Postgres will not allow string literals to contain NUL nor will
SQLite so BLOBs will not dump correctly.

* Schemas should not be set on the destination dump

* MSSQL needs the N prefix to correctly ensure that UTF-8 data is
correctly transferred.

Signed-off-by: Andrew Thornton <art27@cantab.net>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2091
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-committed-by: Andrew Thornton <art27@cantab.net>
2022-01-07 16:04:01 +08:00
Lunny Xiao 2fa7130704 Improve find interface (#2092)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2092
2022-01-06 19:43:06 +08:00
Andrew Thornton 470807151d fix cross db dumping of bools (#2089)
When dumping booleans these need to be converted from the original DB representation
to the new db representation. In the case of most DBs this is simply to 0 or 1 but
for postgres these have to be false or true.

Signed-off-by: Andrew Thornton <art27@cantab.net>

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2089
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Andrew Thornton <art27@cantab.net>
Co-committed-by: Andrew Thornton <art27@cantab.net>
2022-01-05 09:37:18 +08:00
satorunooshie 57365108ae Fix insertMultipleStruct to insert null value under certain circumstances (#2077)
The behavior of multi insertion of null differs from that of single insertion.
On the other hand, behavior between single and bulk insertion of null using pointer of struct field is identical.
Please see the example below.
```go
s := engineGroup.NewSession()

type User struct {
    ID   int    `xorm:"not null pk autoincr INT(10)"`
    Name string `xorm:"not null VARCHAR(191)"`
    Age  int64  `xorm:"null BIGINT(20)"`
}
list := []*User{
    {
        Name: "John",
    },
    {
    	Name: "Wick",
    },
}
s.Nullable("age")

// Single insertion works
_, err := s.Insert(list[0]) // [table] `user` INSERT INTO `user` (`name`,`age`) VALUES (?, ?) [John <nil>]

s.Nullable("age")
// Bulk insertion does not work
_, err := s.Insert(list) // [table] `user` INSERT INTO `user` (`name,`age`) VALUES (?, ?),(?, ?) [John, 0, Wick, 0]

//---------------------------------
//Using pointer, which is nullable, the generated sql has no difference.
//---------------------------------

type User struct {
    ID   int    `xorm:"not null pk autoincr INT(10)"`
    Name string `xorm:"not null VARCHAR(191)"`
    Age  *int64 `xorm:"null BIGINT(20)"`
}
list := []*User{
    {
        Name: "John",
    },
    {
    	Name: "Wick",
    },
}
s.Nullable("age")
// Single insertion works
_, err := s.Insert(list[0]) // [table] `user` INSERT INTO `user` (`name`,`age`) VALUES (?, ?) [John <nil>]

s.Nullable("age")
// Bulk insertion does not work
_, err := s.Insert(list) // [table] `user` INSERT INTO `user` (`name,`age`) VALUES (?, ?),(?, ?) [John, <nil>, Wick, <nil>]
```

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2077
Co-authored-by: satorunooshie <satorunooshie@noreply.gitea.io>
Co-committed-by: satorunooshie <satorunooshie@noreply.gitea.io>
2021-12-27 10:11:44 +08:00
Lunny Xiao a2d3669edf Add README and fix some lints (#2079)
as title.

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2079
2021-12-14 09:00:35 +08:00
Lunny Xiao 303b1aeb77 Upgrade dependencies modules (#2078)
- github.com/goccy/go-json v0.7.4 -> v0.8.1
- github.com/json-iterator/go v1.1.11 -> v1.1.12
- github.com/mattn/go-sqlite3 v1.14.8 -> v1.14.9
- modernc.org/sqlite v1.11.2 -> v1.14.2

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2078
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-12-06 13:13:47 +08:00
linbaozhong 4f8f829913 exist方法sql语句优化 (#2075)
本地没有mssql和oracle,所以没法测试,但是,mysql 使用select 1 from TABLENAME where CONDITION limit 1可能更好

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2075
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: linbaozhong <linbaozhong@noreply.gitea.io>
Co-committed-by: linbaozhong <linbaozhong@noreply.gitea.io>
2021-12-01 13:54:47 +08:00
RenKanai 885f582677 Fix to add session.statement.IsForUpdate check in Session.queryRows() (#2064)
# Issue
The 'for-update' query is executed to slave DB node.

# Example

```go
s := engineGroup.NewSession(); // create session from EngineGroup.
...

s.ForUpdate();

type User struct { ... };
var user User;
has, err := s.Get(&user); // executed to slave DB node.
...
```

Co-authored-by: rennnosuke <rennnosuke@noreply.gitea.io>
Co-authored-by: RenKanai <turutekab@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2064
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: RenKanai <rennnosuke@noreply.gitea.io>
Co-committed-by: RenKanai <rennnosuke@noreply.gitea.io>
2021-11-24 10:29:39 +08:00
Lunny Xiao f35ff7c2eb Make SQLType2Type support uint (#2071)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2071
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-11-19 12:32:55 +08:00
fanybook aea91cc7de add table & column comment for postgres(add table comment for mysql) (#2067)
让 postgres 支持字段注释,只在 v1.2.5 上测试过(不知道怎么 import master)

发现 master 分支好像大改了?模式表名带 schema 了

使用方式和 mysql 相同
```go
	type User struct {
		Id int64	`xorm:"pk autoincr"`
		Name string `json:"name" xorm:"not null default '' varchar(50) index(name_age) comment('用户 (it''s) 1; 名')"`
		Salt string
		Age int		`json:"age" xorm:"not null default 0 int(10) index(name_age) comment('年龄')"`
		Passwd string `xorm:"varchar(200)"`
		CreatedAt time.Time `xorm:"created"`
		UpdatedAt time.Time `xorm:"updated"`
	}

	_ = engine.Sync(new(User))

    func (model User) TableComment() string {
    	return "表注释"
    }
```

Co-authored-by: fanybook <fanybook@gmail.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2067
Co-authored-by: fanybook <fanybook@noreply.gitea.io>
Co-committed-by: fanybook <fanybook@noreply.gitea.io>
2021-11-12 20:58:05 +08:00
stepbystep2 a22f5dce83 少了个`.` (#2068)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2068
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: stepbystep2 <stepbystep2@noreply.gitea.io>
Co-committed-by: stepbystep2 <stepbystep2@noreply.gitea.io>
2021-11-09 17:55:37 +08:00
RenKanai 5feff03a17 Fix README.md: Sync2 -> Sync (#2065)
Co-authored-by: rennnosuke <rennnosuke@noreply.gitea.io>
Co-authored-by: RenKanai <turutekab@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2065
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: RenKanai <rennnosuke@noreply.gitea.io>
Co-committed-by: RenKanai <rennnosuke@noreply.gitea.io>
2021-10-29 17:31:07 +08:00
rennnosuke 26f9d619cc Fix new-lined query execution in master DB node. (#2066)
# Issue
Such a following query is executed in master DB node with EngineGroup.

```go
s := engineGroup.NewSession(); // create session from EngineGroup.

sql := `
SELECT * FROM USER;
`;

type User struct { ... };
var users []User;
err := s.Sql(sql).Find(&users); // executed to master DB node.

```

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2066
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: rennnosuke <rennnosuke@noreply.gitea.io>
Co-committed-by: rennnosuke <rennnosuke@noreply.gitea.io>
2021-10-28 21:21:38 +08:00
Lunny Xiao 40a135948b New Prepare useage (#2061)
Fix #2060, Three ways to use the `Prepare`.

The first

```go
engine.Prepare().Where().Get()
```

The second

```go
sess := engine.NewSession()
defer sess.Close()

sess.Prepare().Where().Get()

sess.Prepare().Where().Get()
```

The third
```go
sess := engine.NewSession()
defer sess.Close()

sess.Begin()

sess.Prepare().Where().Get()

sess.Prepare().Where().Get()

sess.Commit()
```

Or

```go
sess := engine.NewSession()
defer sess.Close()

sess.Begin()

sess.Prepare().Insert()

sess.Prepare().Insert()

sess.Commit()
```

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2061
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-10-20 08:53:30 +08:00
Lunny Xiao b350c289f8 Fix missing quote on modifycolumnSQL (#2058)
Fix #2054

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2058
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-09-29 20:07:55 +08:00
Lunny Xiao bd5cd8cab7 Fix bug of dameng scan (#2056)
Fix #2055

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2056
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-09-24 21:45:47 +08:00
undefined_ss 0de285680b fix ctx override bug (#2053)
详情请参考工单:https://gitea.com/xorm/xorm/issues/2052

Co-authored-by: undefined_ss <sununiq@163.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2053
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: undefined_ss <undefined_ss@noreply.gitea.io>
Co-committed-by: undefined_ss <undefined_ss@noreply.gitea.io>
2021-09-23 20:22:14 +08:00
Lunny Xiao fd26f415ca Add test for mysql tls (#2049)
Fix #1495

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2049
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-09-17 18:47:30 +08:00
Lunny Xiao 6761699036 Fix bug of Rows (#2048)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2048
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-09-16 23:59:36 +08:00
Lunny Xiao 4656a87f2f Fix bug (#2046)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2046
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-09-13 15:19:59 +08:00
Lunny Xiao 16cf2442f6 Some performance optimization for get (#2043)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2043
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-09-07 16:03:08 +08:00
daisuzu 78309606d4 fix panic when `Iterate()` fails (#2040)
not to call `rows.Err()`

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2040
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: daisuzu <daisuzu@noreply.gitea.io>
Co-committed-by: daisuzu <daisuzu@noreply.gitea.io>
2021-09-07 09:23:16 +08:00
Lunny Xiao 045abb07df Expose ScanString / ScanInterface and etc (#2039)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2039
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-08-27 17:10:16 +08:00
finelog e5c89cf55e fix panic when convert sql and args with nil time.Time pointer (#2038)
this pr fix a panic, when using nil time.Time pointer for input.
not sure if there are others similar code.
please review it, thanks!

Co-authored-by: finelog <kaicltw@gmail.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2038
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: finelog <finelog@noreply.gitea.io>
Co-committed-by: finelog <finelog@noreply.gitea.io>
2021-08-26 00:03:18 +08:00
Lunny Xiao 0a429a241d Drop sync function and rename sync2 to sync (#2018)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2018
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-08-24 17:13:17 +08:00
Lunny Xiao d92fb412ee Make Get and Rows.Scan accept multiple parameters (#2029)
Now the below behaviours are allowed.

```Go
var id int64
var name string
has, err := engine.Table(&user).Cols("id", "name").Get(&id, &name)
// SELECT id, name FROM user LIMIT 1
```

```Go
rows, err := engine.Cols("name", "age").Rows(&User{Name:name})
// SELECT * FROM user
defer rows.Close()
for rows.Next() {
    var name string
    var age int
    err = rows.Scan(&name, &age)
}
```

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2029
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-08-24 15:42:34 +08:00
Lunny Xiao c29b9649a9 Add dameng support (#2007)
driver: https://gitee.com/travelliu/dm
docker: https://download.dameng.com/eco/dm8/dm8_docker.tar

fix #1837

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2007
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-08-24 13:46:08 +08:00
yedf 7cd6a74c9f expose sql.Tx (#2036)
Co-authored-by: yedongfu <dongfuye@163.com>
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2036
Co-authored-by: yedf <yedf@noreply.gitea.io>
Co-committed-by: yedf <yedf@noreply.gitea.io>
2021-08-21 11:30:31 +08:00
Lunny Xiao 7d458b4fcf refactor create table for postgres (#2034)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2034
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-08-14 10:57:47 +08:00
Lunny Xiao dbc2de380b Refactor find (#2031)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2031
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-08-11 21:17:23 +08:00
Lunny Xiao 289c27ebec
add changelog for 1.2.2 2021-08-11 09:29:27 +08:00
Lunny Xiao cace6862e2 Move convert back to xorm.io/xorm/convert (#2030)
Since `conversion.Conversion` has been referenced by external package, it should not be moved as internal package.

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2030
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-08-10 23:20:53 +08:00
Lunny Xiao fae164488c Fix problem on README (#2028)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2028
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-08-08 15:35:44 +08:00
Lunny Xiao 3a8ae761c5
Add changelog for v1.2.1 2021-08-08 11:01:02 +08:00
Lunny Xiao ad1a386b5e Fix wrong comment (#2027)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2027
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-08-08 11:00:11 +08:00
Lunny Xiao 4499c8c5b5 Fix import file bug (#2025)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2025
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-08-06 11:26:22 +08:00
Lunny Xiao bd1ca33ea5 Update docs for pgx (#2024)
Reviewed-on: https://gitea.com/xorm/xorm/pulls/2024
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-08-05 14:47:25 +08:00
Lunny Xiao 0b3cc25a10 Add pgx driver support (#1795)
Fix #858

Reviewed-on: https://gitea.com/xorm/xorm/pulls/1795
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-08-05 14:04:11 +08:00
Lunny Xiao 42c2f158e8 Fix timesatmp (#2021)
Now `Datetime` support `Datime(6)` to indicate the time scale. For different database , the max scale is not different.

`Datetime` means `Datetime(0)`.

Reviewed-on: https://gitea.com/xorm/xorm/pulls/2021
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-committed-by: Lunny Xiao <xiaolunwen@gmail.com>
2021-08-04 16:12:10 +08:00
147 changed files with 11274 additions and 4396 deletions

View File

@ -1,364 +0,0 @@
---
kind: pipeline
name: test-mysql
environment:
GO111MODULE: "on"
GOPROXY: "https://goproxy.io"
CGO_ENABLED: 1
trigger:
ref:
- refs/heads/master
- refs/pull/*/head
steps:
- name: test-vet
image: golang:1.15
pull: always
volumes:
- name: cache
path: /go/pkg/mod
commands:
- make vet
- name: test-sqlite3
image: golang:1.15
volumes:
- name: cache
path: /go/pkg/mod
depends_on:
- test-vet
commands:
- make fmt-check
- make test
- make test-sqlite3
- TEST_CACHE_ENABLE=true make test-sqlite3
- name: test-sqlite
image: golang:1.15
volumes:
- name: cache
path: /go/pkg/mod
depends_on:
- test-vet
commands:
- make test-sqlite
- TEST_QUOTE_POLICY=reserved make test-sqlite
- name: test-mysql
image: golang:1.15
pull: never
volumes:
- name: cache
path: /go/pkg/mod
depends_on:
- test-vet
environment:
TEST_MYSQL_HOST: mysql
TEST_MYSQL_CHARSET: utf8
TEST_MYSQL_DBNAME: xorm_test
TEST_MYSQL_USERNAME: root
TEST_MYSQL_PASSWORD:
commands:
- TEST_CACHE_ENABLE=true make test-mysql
- name: test-mysql-utf8mb4
image: golang:1.15
pull: never
volumes:
- name: cache
path: /go/pkg/mod
depends_on:
- test-mysql
environment:
TEST_MYSQL_HOST: mysql
TEST_MYSQL_CHARSET: utf8mb4
TEST_MYSQL_DBNAME: xorm_test
TEST_MYSQL_USERNAME: root
TEST_MYSQL_PASSWORD:
commands:
- make test-mysql
- TEST_QUOTE_POLICY=reserved make test-mysql
volumes:
- name: cache
host:
path: /tmp/cache
services:
- name: mysql
image: mysql:5.7
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: xorm_test
---
kind: pipeline
name: test-mysql8
depends_on:
- test-mysql
trigger:
ref:
- refs/heads/master
- refs/pull/*/head
steps:
- name: test-mysql8
image: golang:1.15
pull: never
volumes:
- name: cache
path: /go/pkg/mod
environment:
TEST_MYSQL_HOST: mysql8
TEST_MYSQL_CHARSET: utf8mb4
TEST_MYSQL_DBNAME: xorm_test
TEST_MYSQL_USERNAME: root
TEST_MYSQL_PASSWORD:
commands:
- make test-mysql
- TEST_CACHE_ENABLE=true make test-mysql
volumes:
- name: cache
host:
path: /tmp/cache
services:
- name: mysql8
image: mysql:8.0
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: xorm_test
---
kind: pipeline
name: test-mariadb
depends_on:
- test-mysql8
trigger:
ref:
- refs/heads/master
- refs/pull/*/head
steps:
- name: test-mariadb
image: golang:1.15
pull: never
volumes:
- name: cache
path: /go/pkg/mod
environment:
TEST_MYSQL_HOST: mariadb
TEST_MYSQL_CHARSET: utf8mb4
TEST_MYSQL_DBNAME: xorm_test
TEST_MYSQL_USERNAME: root
TEST_MYSQL_PASSWORD:
commands:
- make test-mysql
- TEST_QUOTE_POLICY=reserved make test-mysql
volumes:
- name: cache
host:
path: /tmp/cache
services:
- name: mariadb
image: mariadb:10.4
environment:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: xorm_test
---
kind: pipeline
name: test-postgres
depends_on:
- test-mariadb
trigger:
ref:
- refs/heads/master
- refs/pull/*/head
steps:
- name: test-postgres
pull: never
image: golang:1.15
volumes:
- name: cache
path: /go/pkg/mod
environment:
TEST_PGSQL_HOST: pgsql
TEST_PGSQL_DBNAME: xorm_test
TEST_PGSQL_USERNAME: postgres
TEST_PGSQL_PASSWORD: postgres
commands:
- make test-postgres
- TEST_CACHE_ENABLE=true make test-postgres
- name: test-postgres-schema
pull: never
image: golang:1.15
volumes:
- name: cache
path: /go/pkg/mod
depends_on:
- test-postgres
environment:
TEST_PGSQL_HOST: pgsql
TEST_PGSQL_SCHEMA: xorm
TEST_PGSQL_DBNAME: xorm_test
TEST_PGSQL_USERNAME: postgres
TEST_PGSQL_PASSWORD: postgres
commands:
- TEST_QUOTE_POLICY=reserved make test-postgres
volumes:
- name: cache
host:
path: /tmp/cache
services:
- name: pgsql
image: postgres:9.5
environment:
POSTGRES_DB: xorm_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
---
kind: pipeline
name: test-mssql
depends_on:
- test-postgres
trigger:
ref:
- refs/heads/master
- refs/pull/*/head
steps:
- name: test-mssql
pull: never
image: golang:1.15
volumes:
- name: cache
path: /go/pkg/mod
environment:
TEST_MSSQL_HOST: mssql
TEST_MSSQL_DBNAME: xorm_test
TEST_MSSQL_USERNAME: sa
TEST_MSSQL_PASSWORD: "yourStrong(!)Password"
commands:
- make test-mssql
- TEST_MSSQL_DEFAULT_VARCHAR=NVARCHAR TEST_MSSQL_DEFAULT_CHAR=NCHAR make test-mssql
volumes:
- name: cache
host:
path: /tmp/cache
services:
- name: mssql
pull: always
image: mcr.microsoft.com/mssql/server:latest
environment:
ACCEPT_EULA: Y
SA_PASSWORD: yourStrong(!)Password
MSSQL_PID: Standard
---
kind: pipeline
name: test-tidb
depends_on:
- test-mssql
trigger:
ref:
- refs/heads/master
- refs/pull/*/head
steps:
- name: test-tidb
pull: never
image: golang:1.15
volumes:
- name: cache
path: /go/pkg/mod
environment:
TEST_TIDB_HOST: "tidb:4000"
TEST_TIDB_DBNAME: xorm_test
TEST_TIDB_USERNAME: root
TEST_TIDB_PASSWORD:
commands:
- make test-tidb
volumes:
- name: cache
host:
path: /tmp/cache
services:
- name: tidb
image: pingcap/tidb:v3.0.3
---
kind: pipeline
name: test-cockroach
depends_on:
- test-tidb
trigger:
ref:
- refs/heads/master
- refs/pull/*/head
steps:
- name: test-cockroach
pull: never
image: golang:1.15
volumes:
- name: cache
path: /go/pkg/mod
environment:
TEST_COCKROACH_HOST: "cockroach:26257"
TEST_COCKROACH_DBNAME: xorm_test
TEST_COCKROACH_USERNAME: root
TEST_COCKROACH_PASSWORD:
commands:
- sleep 10
- make test-cockroach
volumes:
- name: cache
host:
path: /tmp/cache
services:
- name: cockroach
image: cockroachdb/cockroach:v19.2.4
commands:
- /cockroach/cockroach start --insecure
---
kind: pipeline
name: merge_coverage
depends_on:
- test-mysql
- test-mysql8
- test-mariadb
- test-postgres
- test-mssql
- test-tidb
- test-cockroach
trigger:
ref:
- refs/heads/master
- refs/pull/*/head
steps:
- name: merge_coverage
image: golang:1.15
commands:
- make coverage
---
kind: pipeline
name: release-tag
trigger:
event:
- tag
steps:
- name: release-tag-gitea
pull: always
image: plugins/gitea-release:latest
settings:
base_url: https://gitea.com
title: '${DRONE_TAG} is released'
api_key:
from_secret: gitea_token

View File

@ -0,0 +1,23 @@
name: release
on:
push:
tags:
- '*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: setup go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: Use Go Action
id: use-go-action
uses: https://gitea.com/actions/release-action@main
with:
api_key: '${{secrets.RELEASE_TOKEN}}'

View File

@ -0,0 +1,33 @@
name: test cockroach
on:
push:
branches:
- donttrigger # disabled for now
#- main
#- v1
#pull_request:
jobs:
test-cockroach:
name: test cockroach
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: test cockroach
env:
TEST_COCKROACH_HOST: "cockroach:26257"
TEST_COCKROACH_DBNAME: xorm_test
TEST_COCKROACH_USERNAME: root
TEST_COCKROACH_PASSWORD:
IGNORE_TEST_DELETE_LIMIT: true
run: sleep 20 && make test-cockroach
services:
cockroach:
image: cockroachdb/cockroach:v19.2.4
cmd:
- 'start'
- '--insecure'

View File

@ -0,0 +1,32 @@
name: test mariadb
on:
push:
branches:
- main
- v1
pull_request:
jobs:
lint:
name: test mariadb
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: test mariadb
env:
TEST_MYSQL_HOST: mariadb
TEST_MYSQL_CHARSET: utf8mb4
TEST_MYSQL_DBNAME: xorm_test
TEST_MYSQL_USERNAME: root
TEST_MYSQL_PASSWORD:
run: TEST_QUOTE_POLICY=reserved make test-mysql
services:
mariadb:
image: mariadb:10.4
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: xorm_test

View File

@ -0,0 +1,33 @@
name: test mssql
on:
push:
branches:
- main
- v1
pull_request:
jobs:
test-mssql-collation:
name: test mssql with collation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: test mssql with collation
env:
TEST_MSSQL_HOST: mssql2
TEST_MSSQL_DBNAME: xorm_test
TEST_MSSQL_USERNAME: sa
TEST_MSSQL_PASSWORD: "yourStrong(!)Password"
TEST_MSSQL_COLLATION: SQL_Latin1_General_CP1_CS_AS
run: TEST_MSSQL_DEFAULT_VARCHAR=NVARCHAR TEST_MSSQL_DEFAULT_CHAR=NCHAR make test-mssql
services:
mssql2:
image: mcr.microsoft.com/mssql/server:latest
env:
ACCEPT_EULA: Y
SA_PASSWORD: yourStrong(!)Password
MSSQL_PID: Standard

View File

@ -0,0 +1,32 @@
name: test mssql
on:
push:
branches:
- main
- v1
pull_request:
jobs:
test-mssql:
name: test mssql
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: test mssql
env:
TEST_MSSQL_HOST: mssql
TEST_MSSQL_DBNAME: xorm_test
TEST_MSSQL_USERNAME: sa
TEST_MSSQL_PASSWORD: "yourStrong(!)Password"
run: TEST_MSSQL_DEFAULT_VARCHAR=NVARCHAR TEST_MSSQL_DEFAULT_CHAR=NCHAR make test-mssql
services:
mssql:
image: mcr.microsoft.com/mssql/server:latest
env:
ACCEPT_EULA: Y
SA_PASSWORD: yourStrong(!)Password
MSSQL_PID: Standard

View File

@ -0,0 +1,32 @@
name: test mysql
on:
push:
branches:
- main
- v1
pull_request:
jobs:
test-mysql:
name: test mysql
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: test mysql utf8mb4
env:
TEST_MYSQL_HOST: mysql
TEST_MYSQL_CHARSET: utf8mb4
TEST_MYSQL_DBNAME: xorm_test
TEST_MYSQL_USERNAME: root
TEST_MYSQL_PASSWORD:
run: TEST_QUOTE_POLICY=reserved make test-mysql-tls
services:
mysql:
image: mysql:5.7
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: xorm_test

View File

@ -0,0 +1,32 @@
name: test mysql8
on:
push:
branches:
- main
- v1
pull_request:
jobs:
lint:
name: test mysql8
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: test mysql8
env:
TEST_MYSQL_HOST: mysql8
TEST_MYSQL_CHARSET: utf8mb4
TEST_MYSQL_DBNAME: xorm_test
TEST_MYSQL_USERNAME: root
TEST_MYSQL_PASSWORD:
run: TEST_CACHE_ENABLE=true make test-mysql
services:
mysql8:
image: mysql:8.0
env:
MYSQL_ALLOW_EMPTY_PASSWORD: yes
MYSQL_DATABASE: xorm_test

View File

@ -0,0 +1,55 @@
name: test postgres
on:
push:
branches:
- main
- v1
pull_request:
jobs:
lint:
name: test postgres
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: test postgres
env:
TEST_PGSQL_HOST: pgsql
TEST_PGSQL_DBNAME: xorm_test
TEST_PGSQL_USERNAME: postgres
TEST_PGSQL_PASSWORD: postgres
run: TEST_CACHE_ENABLE=true make test-postgres
- name: test postgres with schema
env:
TEST_PGSQL_HOST: pgsql
TEST_PGSQL_SCHEMA: xorm
TEST_PGSQL_DBNAME: xorm_test
TEST_PGSQL_USERNAME: postgres
TEST_PGSQL_PASSWORD: postgres
run: TEST_QUOTE_POLICY=reserved make test-postgres
- name: test pgx
env:
TEST_PGSQL_HOST: pgsql
TEST_PGSQL_DBNAME: xorm_test
TEST_PGSQL_USERNAME: postgres
TEST_PGSQL_PASSWORD: postgres
run: TEST_CACHE_ENABLE=true make test-pgx
- name: test pgx with schema
env:
TEST_PGSQL_HOST: pgsql
TEST_PGSQL_SCHEMA: xorm
TEST_PGSQL_DBNAME: xorm_test
TEST_PGSQL_USERNAME: postgres
TEST_PGSQL_PASSWORD: postgres
run: TEST_QUOTE_POLICY=reserved make test-pgx
services:
pgsql:
image: postgres:9.5
env:
POSTGRES_DB: xorm_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres

View File

@ -0,0 +1,37 @@
name: test sqlite
on:
push:
branches:
- main
- v1
pull_request:
jobs:
test-sqlite:
name: unit test & test sqlite
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: vet
run: make vet
- name: format check
run: make fmt-check
- name: unit test
run: make test
- name: test sqlite3
run: make test-sqlite3
- name: test sqlite3 with cache
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: ./...

View File

@ -0,0 +1,28 @@
name: test tidb
on:
push:
branches:
- main
- v1
pull_request:
jobs:
test-tidb:
name: test tidb
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- name: test tidb
env:
TEST_TIDB_HOST: "tidb:4000"
TEST_TIDB_DBNAME: xorm_test
TEST_TIDB_USERNAME: root
TEST_TIDB_PASSWORD:
run: make test-tidb
services:
tidb:
image: pingcap/tidb:v3.0.3

7
.gitignore vendored
View File

@ -35,6 +35,7 @@ test.db.sql
*coverage.out
test.db
integrations/*.sql
integrations/test_sqlite*
cover.out
tests/*.sql
tests/test_sqlite*
cover.out
cover.html

24
.golangci.yml Normal file
View File

@ -0,0 +1,24 @@
linters:
enable:
- gosimple
- deadcode
- typecheck
- govet
- errcheck
- staticcheck
- unused
- structcheck
- varcheck
- dupl
#- gocyclo # The cyclomatic complexety of a lot of functions is too high, we should refactor those another time.
- gofmt
- misspell
- gocritic
- bidichk
- ineffassign
enable-all: false
disable-all: true
fast: false
run:
timeout: 3m

View File

@ -1,29 +0,0 @@
ignoreGeneratedHeader = false
severity = "warning"
confidence = 0.8
errorCode = 1
warningCode = 1
[rule.blank-imports]
[rule.context-as-argument]
[rule.context-keys-type]
[rule.dot-imports]
[rule.empty-lines]
[rule.errorf]
[rule.error-return]
[rule.error-strings]
[rule.error-naming]
[rule.exported]
[rule.if-return]
[rule.increment-decrement]
[rule.indent-error-flow]
[rule.package-comments]
[rule.range]
[rule.receiver-naming]
[rule.struct-tag]
[rule.time-naming]
[rule.unexported-return]
[rule.unnecessary-stmt]
[rule.var-declaration]
[rule.var-naming]
arguments = [["ID", "UID", "UUID", "URL", "JSON"], []]

View File

@ -3,6 +3,70 @@
This changelog goes through all the changes that have been made in each release
without substantial changes to our git log.
## [1.3.2](https://gitea.com/xorm/xorm/releases/tag/1.3.2) - 2022-09-03
* BUGFIXES
* Change schemas.Column to use int64 (#2160)
* MISC
* Prevent Sync failure with non-regular indexes on Postgres (#2174)
## [1.3.1](https://gitea.com/xorm/xorm/releases/tag/1.3.1) - 2022-06-03
* BREAKING
* Refactor orderby and support arguments (#2150)
* return a clear error for set TEXT type as compare condition (#2062)
* BUGFIXES
* Fix oid index for postgres (#2154)
* Add ORDER BY SEQ_IN_INDEX to MySQL GetIndexes to Fix IndexTests (#2152)
* some improvement (#2136)
* ENHANCEMENTS
* Add interface to allow structs to provide specific index information (#2137)
* MySQL/MariaDB: return max length for text columns (#2133)
* PostgreSQL: enable comment on column (#2131)
* TESTING
* Add test for find date (#2121)
## [1.3.0](https://gitea.com/xorm/xorm/releases/tag/1.3.0) - 2022-04-14
* BREAKING
* New Prepare useage (#2061)
* Make Get and Rows.Scan accept multiple parameters (#2029)
* Drop sync function and rename sync2 to sync (#2018)
* FEATURES
* Add dameng support (#2007)
* BUGFIXES
* bugfix :Oid It's a special index. You can't put it in (#2105)
* Fix new-lined query execution in master DB node. (#2066)
* Fix bug of Rows (#2048)
* Fix bug (#2046)
* fix panic when `Iterate()` fails (#2040)
* fix panic when convert sql and args with nil time.Time pointer (#2038)
* ENHANCEMENTS
* Fix to add session.statement.IsForUpdate check in Session.queryRows() (#2064)
* Expose ScanString / ScanInterface and etc (#2039)
* TESTING
* Add test for mysql tls (#2049)
* BUILD
* Upgrade dependencies modules (#2078)
* MISC
* Fix oracle keyword AS (#2109)
* Some performance optimization for get (#2043)
## [1.2.2](https://gitea.com/xorm/xorm/releases/tag/1.2.2) - 2021-08-11
* MISC
* Move convert back to xorm.io/xorm/convert (#2030)
## [1.2.1](https://gitea.com/xorm/xorm/releases/tag/1.2.1) - 2021-08-08
* FEATURES
* Add pgx driver support (#1795)
* BUGFIXES
* Fix wrong comment (#2027)
* Fix import file bug (#2025)
* ENHANCEMENTS
* Fix timesatmp (#2021)
## [1.2.0](https://gitea.com/xorm/xorm/releases/tag/1.2.0) - 2021-08-04
* BREAKING

View File

@ -1,13 +1,13 @@
## Contributing to xorm
`xorm` has a backlog of [pull requests](https://help.github.com/articles/using-pull-requests), but contributions are still very
much welcome. You can help with patch review, submitting bug reports,
`xorm` has a backlog of [pull requests](https://gitea.com/xorm/xorm/pulls), but contributions are still very
much welcome. You can help with patch review, submitting [bug reports](https://gitea.com/xorm/xorm/issues),
or adding new functionality. There is no formal style guide, but
please conform to the style of existing code and general Go formatting
conventions when submitting patches.
* [fork a repo](https://help.github.com/articles/fork-a-repo)
* [creating a pull request ](https://help.github.com/articles/creating-a-pull-request)
* [fork the repo](https://gitea.com/repo/fork/2038)
* [creating a pull request ](https://docs.gitea.io/en-us/pull-request/)
### Language
@ -15,7 +15,7 @@ Since `xorm` is a world-wide open source project, please describe your issues or
### Sign your codes with comments
```
// !<you github id>! your comments
// !<your gitea.com id>! your comments
e.g.,
@ -65,7 +65,7 @@ And if your branch is related with cache, you could also enable it via `TEST_CAC
### Patch review
Help review existing open [pull requests](https://help.github.com/articles/using-pull-requests) by commenting on the code or
Help review existing open [pull requests](https://gitea.com/xorm/xorm/pulls) by commenting on the code or
proposed functionality.
### Bug reports

View File

@ -9,7 +9,7 @@ SED_INPLACE := sed -i
GO_DIRS := caches contexts integrations core dialects internal log migrate names schemas tags
GOFILES := $(wildcard *.go)
GOFILES += $(shell find $(GO_DIRS) -name "*.go" -type f)
INTEGRATION_PACKAGES := xorm.io/xorm/integrations
INTEGRATION_PACKAGES := xorm.io/xorm/tests
PACKAGES ?= $(filter-out $(INTEGRATION_PACKAGES),$(shell $(GO) list ./...))
TEST_COCKROACH_HOST ?= cockroach:26257
@ -25,6 +25,7 @@ TEST_MSSQL_PASSWORD ?= MwantsaSecurePassword1
TEST_MSSQL_DEFAULT_VARCHAR ?= varchar
TEST_MSSQL_DEFAULT_CHAR ?= char
TEST_MSSQL_DO_NVARCHAR_OVERRIDE_TEST ?= true
TEST_MSSQL_COLLATION ?=
TEST_MYSQL_HOST ?= mysql:3306
TEST_MYSQL_CHARSET ?= utf8
@ -43,6 +44,10 @@ TEST_TIDB_DBNAME ?= xorm_test
TEST_TIDB_USERNAME ?= root
TEST_TIDB_PASSWORD ?=
TEST_DAMENG_HOST ?= dameng:5236
TEST_DAMENG_USERNAME ?= SYSDBA
TEST_DAMENG_PASSWORD ?= SYSDBA
TEST_CACHE_ENABLE ?= false
TEST_QUOTE_POLICY ?= always
@ -56,7 +61,7 @@ build: go-check $(GO_SOURCES)
.PHONY: clean
clean:
$(GO) clean -i ./...
rm -rf *.sql *.log test.db *coverage.out coverage.all integrations/*.sql
rm -rf *.sql *.log test.db cover.out cover.html *coverage.out coverage.all integrations/*.sql
.PHONY: coverage
coverage:
@ -94,8 +99,7 @@ help:
@echo " - build creates the entire project"
@echo " - clean delete integration files and build files but not css and js files"
@echo " - fmt format the code"
@echo " - lint run code linter revive"
@echo " - misspell check if a word is written wrong"
@echo " - lint run code linter"
@echo " - test run default unit test"
@echo " - test-cockroach run integration tests for cockroach"
@echo " - test-mysql run integration tests for mysql"
@ -107,32 +111,30 @@ help:
@echo " - vet examines Go source code and reports suspicious constructs"
.PHONY: lint
lint: revive
lint: golangci-lint
.PHONY: revive
revive:
@hash revive > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u github.com/mgechev/revive; \
fi
revive -config .revive.toml -exclude=./vendor/... ./... || exit 1
.PHONY: golangci-lint
golangci-lint: golangci-lint-check
golangci-lint run --timeout 10m
.PHONY: misspell
misspell:
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
.PHONY: golangci-lint-check
golangci-lint-check:
$(eval GOLANGCI_LINT_VERSION := $(shell printf "%03d%03d%03d" $(shell golangci-lint --version | grep -Eo '[0-9]+\.[0-9.]+' | tr '.' ' ');))
$(eval MIN_GOLANGCI_LINT_VER_FMT := $(shell printf "%g.%g.%g" $(shell echo $(MIN_GOLANGCI_LINT_VERSION) | grep -o ...)))
@hash golangci-lint > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
echo "Downloading golangci-lint v${MIN_GOLANGCI_LINT_VER_FMT}"; \
export BINARY="golangci-lint"; \
curl -sfL "https://raw.githubusercontent.com/golangci/golangci-lint/v${MIN_GOLANGCI_LINT_VER_FMT}/install.sh" | sh -s -- -b $(GOPATH)/bin v$(MIN_GOLANGCI_LINT_VER_FMT); \
elif [ "$(GOLANGCI_LINT_VERSION)" -lt "$(MIN_GOLANGCI_LINT_VERSION)" ]; then \
echo "Downloading newer version of golangci-lint v${MIN_GOLANGCI_LINT_VER_FMT}"; \
export BINARY="golangci-lint"; \
curl -sfL "https://raw.githubusercontent.com/golangci/golangci-lint/v${MIN_GOLANGCI_LINT_VER_FMT}/install.sh" | sh -s -- -b $(GOPATH)/bin v$(MIN_GOLANGCI_LINT_VER_FMT); \
fi
misspell -w -i unknwon $(GOFILES)
.PHONY: misspell-check
misspell-check:
@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) get -u github.com/client9/misspell/cmd/misspell; \
fi
misspell -error -i unknwon,destory $(GOFILES)
.PHONY: test
test: go-check
$(GO) test $(PACKAGES)
$(GO) test -cover -coverprofile=cover.out $(PACKAGES)
$(GO) tool cover -html=cover.out -o cover.html
.PNONY: test-cockroach
test-cockroach: go-check
@ -152,6 +154,7 @@ test-mssql: go-check
-conn_str="server=$(TEST_MSSQL_HOST);user id=$(TEST_MSSQL_USERNAME);password=$(TEST_MSSQL_PASSWORD);database=$(TEST_MSSQL_DBNAME)" \
-default_varchar=$(TEST_MSSQL_DEFAULT_VARCHAR) -default_char=$(TEST_MSSQL_DEFAULT_CHAR) \
-do_nvarchar_override_test=$(TEST_MSSQL_DO_NVARCHAR_OVERRIDE_TEST) \
-collation=$(TEST_MSSQL_COLLATION) \
-coverprofile=mssql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m
.PNONY: test-mssql\#%
@ -186,6 +189,18 @@ test-mysql\#%: go-check
-conn_str="$(TEST_MYSQL_USERNAME):$(TEST_MYSQL_PASSWORD)@tcp($(TEST_MYSQL_HOST))/$(TEST_MYSQL_DBNAME)?charset=$(TEST_MYSQL_CHARSET)" \
-coverprofile=mysql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PNONY: test-mysql-tls
test-mysql-tls: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mysql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \
-conn_str="$(TEST_MYSQL_USERNAME):$(TEST_MYSQL_PASSWORD)@tcp($(TEST_MYSQL_HOST))/$(TEST_MYSQL_DBNAME)?charset=$(TEST_MYSQL_CHARSET)&tls=skip-verify" \
-coverprofile=mysql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m
.PHONY: test-mysql-tls\#%
test-mysql-tls\#%: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -db=mysql -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \
-conn_str="$(TEST_MYSQL_USERNAME):$(TEST_MYSQL_PASSWORD)@tcp($(TEST_MYSQL_HOST))/$(TEST_MYSQL_DBNAME)?charset=$(TEST_MYSQL_CHARSET)&tls=skip-verify" \
-coverprofile=mysql.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PNONY: test-postgres
test-postgres: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -db=postgres -schema='$(TEST_PGSQL_SCHEMA)' -cache=$(TEST_CACHE_ENABLE) \
@ -211,7 +226,19 @@ test-sqlite3-schema: go-check
.PHONY: test-sqlite3\#%
test-sqlite3\#%: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite3 -conn_str="./test.db?cache=shared&mode=rwc" \
-quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
-quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite3.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m
.PNONY: test-pgx
test-pgx: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -db=pgx -schema='$(TEST_PGSQL_SCHEMA)' -cache=$(TEST_CACHE_ENABLE) \
-conn_str="postgres://$(TEST_PGSQL_USERNAME):$(TEST_PGSQL_PASSWORD)@$(TEST_PGSQL_HOST)/$(TEST_PGSQL_DBNAME)?sslmode=disable" \
-quote=$(TEST_QUOTE_POLICY) -coverprofile=postgres.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m
.PHONY: test-pgx\#%
test-pgx\#%: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -db=pgx -schema='$(TEST_PGSQL_SCHEMA)' -cache=$(TEST_CACHE_ENABLE) \
-conn_str="postgres://$(TEST_PGSQL_USERNAME):$(TEST_PGSQL_PASSWORD)@$(TEST_PGSQL_HOST)/$(TEST_PGSQL_DBNAME)?sslmode=disable" \
-quote=$(TEST_QUOTE_POLICY) -coverprofile=postgres.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m
.PHONY: test-sqlite
test-sqlite: go-check
@ -228,7 +255,6 @@ test-sqlite\#%: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -cache=$(TEST_CACHE_ENABLE) -db=sqlite -conn_str="./test.db?cache=shared&mode=rwc" \
-quote=$(TEST_QUOTE_POLICY) -coverprofile=sqlite.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PNONY: test-tidb
test-tidb: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -db=mysql -cache=$(TEST_CACHE_ENABLE) -ignore_select_update=true \
@ -241,6 +267,18 @@ test-tidb\#%: go-check
-conn_str="$(TEST_TIDB_USERNAME):$(TEST_TIDB_PASSWORD)@tcp($(TEST_TIDB_HOST))/$(TEST_TIDB_DBNAME)" \
-quote=$(TEST_QUOTE_POLICY) -coverprofile=tidb.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic
.PNONY: test-dameng
test-dameng: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -db=dm -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \
-conn_str="dm://$(TEST_DAMENG_USERNAME):$(TEST_DAMENG_PASSWORD)@$(TEST_DAMENG_HOST)" \
-coverprofile=dameng.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m
.PHONY: test-dameng\#%
test-dameng\#%: go-check
$(GO) test $(INTEGRATION_PACKAGES) -v -race -run $* -db=dm -cache=$(TEST_CACHE_ENABLE) -quote=$(TEST_QUOTE_POLICY) \
-conn_str="dm://$(TEST_DAMENG_USERNAME):$(TEST_DAMENG_PASSWORD)@$(TEST_DAMENG_HOST)" \
-coverprofile=dameng.$(TEST_QUOTE_POLICY).$(TEST_CACHE_ENABLE).coverage.out -covermode=atomic -timeout=20m
.PHONY: vet
vet:
$(GO) vet $(shell $(GO) list ./...)

View File

@ -1,10 +1,10 @@
# xorm
[中文](https://gitea.com/xorm/xorm/src/branch/master/README_CN.md)
[中文](https://gitea.com/xorm/xorm/src/branch/v1/README_CN.md)
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
@ -41,17 +41,22 @@ Drivers for Go's sql package which currently support database/sql includes:
* [Postgres](https://github.com/postgres/postgres) / [Cockroach](https://github.com/cockroachdb/cockroach)
- [github.com/lib/pq](https://github.com/lib/pq)
- [github.com/jackc/pgx](https://github.com/jackc/pgx)
* [SQLite](https://sqlite.org)
- [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3)
- [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) (windows unsupported)
- [modernc.org/sqlite](https://gitlab.com/cznic/sqlite)
* MsSql
- [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb)
- [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/godror/godror](https://github.com/godror/godror) (experiment)
- [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (experiment)
- [github.com/sijms/go-ora](https://github.com/sijms/go-ora) (experiment)
## Installation
@ -73,7 +78,7 @@ Firstly, we should new an engine for a database.
engine, err := xorm.NewEngine(driverName, dataSourceName)
```
* Define a struct and Sync2 table struct to database
* Define a struct and Sync table struct to database
```Go
type User struct {
@ -86,7 +91,7 @@ type User struct {
Updated time.Time `xorm:"updated"`
}
err := engine.Sync2(new(User))
err := engine.Sync(new(User))
```
* Create Engine Group
@ -140,6 +145,24 @@ affected, err := engine.Insert(&users)
affected, err := engine.Insert(&user1, &users)
// INSERT INTO struct1 () values ()
// INSERT INTO struct2 () values (),(),()
affected, err := engine.Table("user").Insert(map[string]interface{}{
"name": "lunny",
"age": 18,
})
// INSERT INTO user (name, age) values (?,?)
affected, err := engine.Table("user").Insert([]map[string]interface{}{
{
"name": "lunny",
"age": 18,
},
{
"name": "lunny2",
"age": 19,
},
})
// INSERT INTO user (name, age) values (?,?),(?,?)
```
* `Get` query one record from database
@ -160,6 +183,11 @@ has, err := engine.Table(&user).Where("name = ?", name).Cols("id").Get(&id)
has, err := engine.SQL("select id from user").Get(&id)
// SELECT id FROM user WHERE name = ?
var id int64
var name string
has, err := engine.Table(&user).Cols("id", "name").Get(&id, &name)
// SELECT id, name FROM user LIMIT 1
var valuesMap = make(map[string]string)
has, err := engine.Table(&user).Where("id = ?", id).Get(&valuesMap)
// SELECT * FROM user WHERE id = ?
@ -233,7 +261,11 @@ err := engine.BufferSize(100).Iterate(&User{Name:name}, func(idx int, bean inter
})
// SELECT * FROM user Limit 0, 100
// SELECT * FROM user Limit 101, 100
```
You can use rows which is similiar with `sql.Rows`
```Go
rows, err := engine.Rows(&User{Name:name})
// SELECT * FROM user
defer rows.Close()
@ -243,6 +275,19 @@ for rows.Next() {
}
```
or
```Go
rows, err := engine.Cols("name", "age").Rows(&User{Name:name})
// SELECT * FROM user
defer rows.Close()
for rows.Next() {
var name string
var age int
err = rows.Scan(&name, &age)
}
```
* `Update` update one or more records, default will update non-empty and non-zero fields except when you use Cols, AllCols and so on.
```Go
@ -423,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

View File

@ -1,6 +1,6 @@
# xorm
[English](https://gitea.com/xorm/xorm/src/branch/master/README.md)
[English](https://gitea.com/xorm/xorm/src/branch/v1/README.md)
xorm 是一个简单而强大的Go语言ORM库. 通过它可以使数据库操作非常简便。
@ -40,17 +40,22 @@ v1.0.0 相对于 v0.8.2 有以下不兼容的变更:
* [Postgres](https://github.com/postgres/postgres) / [Cockroach](https://github.com/cockroachdb/cockroach)
- [github.com/lib/pq](https://github.com/lib/pq)
- [github.com/jackc/pgx](https://github.com/jackc/pgx)
* [SQLite](https://sqlite.org)
- [github.com/mattn/go-sqlite3](https://github.com/mattn/go-sqlite3)
- [modernc.org/sqlite](https://gitlab.com/cznic/sqlite) (Windows试验性支持)
- [modernc.org/sqlite](https://gitlab.com/cznic/sqlite)
* MsSql
- [github.com/denisenkom/go-mssqldb](https://github.com/denisenkom/go-mssqldb)
- [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/godror/godror](https://github.com/godror/godror) (试验性支持)
- [github.com/mattn/go-oci8](https://github.com/mattn/go-oci8) (试验性支持)
- [github.com/sijms/go-ora](https://github.com/sijms/go-ora) (试验性支持)
## 安装
@ -64,7 +69,7 @@ v1.0.0 相对于 v0.8.2 有以下不兼容的变更:
# 快速开始
* 第一步创建引擎driverName, dataSourceName和database/sql接口相同
* 第一步创建引擎,`driverName`, `dataSourceName` `database/sql` 接口相同
```Go
engine, err := xorm.NewEngine(driverName, dataSourceName)
@ -83,7 +88,7 @@ type User struct {
Updated time.Time `xorm:"updated"`
}
err := engine.Sync2(new(User))
err := engine.Sync(new(User))
```
* 创建Engine组
@ -102,7 +107,7 @@ engineGroup, err := xorm.NewEngineGroup(masterEngine, []*Engine{slave1Engine, sl
所有使用 `engine` 都可以简单的用 `engineGroup` 来替换。
* `Query` 最原始的也支持SQL语句查询返回的结果类型为 []map[string][]byte。`QueryString` 返回 []map[string]string, `QueryInterface` 返回 `[]map[string]interface{}`.
* `Query` 最原始的也支持SQL语句查询返回的结果类型为 `[]map[string][]byte`。`QueryString` 返回 `[]map[string]string`, `QueryInterface` 返回 `[]map[string]interface{}`.
```Go
results, err := engine.Query("select * from user")
@ -137,6 +142,24 @@ affected, err := engine.Insert(&users)
affected, err := engine.Insert(&user1, &users)
// INSERT INTO struct1 () values ()
// INSERT INTO struct2 () values (),(),()
affected, err := engine.Table("user").Insert(map[string]interface{}{
"name": "lunny",
"age": 18,
})
// INSERT INTO user (name, age) values (?,?)
affected, err := engine.Table("user").Insert([]map[string]interface{}{
{
"name": "lunny",
"age": 18,
},
{
"name": "lunny2",
"age": 19,
},
})
// INSERT INTO user (name, age) values (?,?),(?,?)
```
* `Get` 查询单条记录
@ -157,6 +180,11 @@ has, err := engine.Table(&user).Where("name = ?", name).Cols("id").Get(&id)
has, err := engine.SQL("select id from user").Get(&id)
// SELECT id FROM user WHERE name = ?
var id int64
var name string
has, err := engine.Table(&user).Cols("id", "name").Get(&id, &name)
// SELECT id, name FROM user LIMIT 1
var valuesMap = make(map[string]string)
has, err := engine.Table(&user).Where("id = ?", id).Get(&valuesMap)
// SELECT * FROM user WHERE id = ?
@ -208,7 +236,7 @@ type UserDetail struct {
}
var users []UserDetail
err := engine.Table("user").Select("user.*, detail.*")
err := engine.Table("user").Select("user.*, detail.*").
Join("INNER", "detail", "detail.user_id = user.id").
Where("user.name = ?", name).Limit(10, 0).
Find(&users)
@ -230,7 +258,11 @@ err := engine.BufferSize(100).Iterate(&User{Name:name}, func(idx int, bean inter
})
// SELECT * FROM user Limit 0, 100
// SELECT * FROM user Limit 101, 100
```
Rows 的用法类似 `sql.Rows`
```Go
rows, err := engine.Rows(&User{Name:name})
// SELECT * FROM user
defer rows.Close()
@ -240,6 +272,19 @@ for rows.Next() {
}
```
或者
```Go
rows, err := engine.Cols("name", "age").Rows(&User{Name:name})
// SELECT * FROM user
defer rows.Close()
for rows.Next() {
var name string
var age int
err = rows.Scan(&name, &age)
}
```
* `Update` 更新数据除非使用Cols,AllCols函数指明默认只更新非空和非0的字段
```Go

View File

@ -16,19 +16,19 @@ import (
// Md5 return md5 hash string
func Md5(str string) string {
m := md5.New()
io.WriteString(m, str)
_, _ = io.WriteString(m, str)
return fmt.Sprintf("%x", m.Sum(nil))
}
// Encode Encode data
func Encode(data interface{}) ([]byte, error) {
//return JsonEncode(data)
// return JsonEncode(data)
return GobEncode(data)
}
// Decode decode data
func Decode(data []byte, to interface{}) error {
//return JsonDecode(data, to)
// return JsonDecode(data, to)
return GobDecode(data, to)
}

View File

@ -56,7 +56,7 @@ func (m *LRUCacher) GC() {
var removedNum int
for e := m.idList.Front(); e != nil; {
if removedNum <= CacheGcMaxRemoved &&
time.Now().Sub(e.Value.(*idNode).lastVisit) > m.Expired {
time.Since(e.Value.(*idNode).lastVisit) > m.Expired {
removedNum++
next := e.Next()
node := e.Value.(*idNode)
@ -70,7 +70,7 @@ func (m *LRUCacher) GC() {
removedNum = 0
for e := m.sqlList.Front(); e != nil; {
if removedNum <= CacheGcMaxRemoved &&
time.Now().Sub(e.Value.(*sqlNode).lastVisit) > m.Expired {
time.Since(e.Value.(*sqlNode).lastVisit) > m.Expired {
removedNum++
next := e.Next()
node := e.Value.(*sqlNode)
@ -96,7 +96,7 @@ func (m *LRUCacher) GetIds(tableName, sql string) interface{} {
} else {
lastTime := el.Value.(*sqlNode).lastVisit
// if expired, remove the node and return nil
if time.Now().Sub(lastTime) > m.Expired {
if time.Since(lastTime) > m.Expired {
m.delIds(tableName, sql)
return nil
}
@ -122,7 +122,7 @@ func (m *LRUCacher) GetBean(tableName string, id string) interface{} {
if el, ok := m.idIndex[tableName][id]; ok {
lastTime := el.Value.(*idNode).lastVisit
// if expired, remove the node and return nil
if time.Now().Sub(lastTime) > m.Expired {
if time.Since(lastTime) > m.Expired {
m.delBean(tableName, id)
return nil
}
@ -145,7 +145,7 @@ func (m *LRUCacher) clearIds(tableName string) {
if tis, ok := m.sqlIndex[tableName]; ok {
for sql, v := range tis {
m.sqlList.Remove(v)
m.store.Del(sql)
_ = m.store.Del(sql)
}
}
m.sqlIndex[tableName] = make(map[string]*list.Element)
@ -163,7 +163,7 @@ func (m *LRUCacher) clearBeans(tableName string) {
for id, v := range tis {
m.idList.Remove(v)
tid := genID(tableName, id)
m.store.Del(tid)
_ = m.store.Del(tid)
}
}
m.idIndex[tableName] = make(map[string]*list.Element)
@ -188,7 +188,7 @@ func (m *LRUCacher) PutIds(tableName, sql string, ids interface{}) {
} else {
el.Value.(*sqlNode).lastVisit = time.Now()
}
m.store.Put(sql, ids)
_ = m.store.Put(sql, ids)
if m.sqlList.Len() > m.MaxElementSize {
e := m.sqlList.Front()
node := e.Value.(*sqlNode)
@ -210,7 +210,7 @@ func (m *LRUCacher) PutBean(tableName string, id string, obj interface{}) {
el.Value.(*idNode).lastVisit = time.Now()
}
m.store.Put(genID(tableName, id), obj)
_ = m.store.Put(genID(tableName, id), obj)
if m.idList.Len() > m.MaxElementSize {
e := m.idList.Front()
node := e.Value.(*idNode)
@ -226,7 +226,7 @@ func (m *LRUCacher) delIds(tableName, sql string) {
m.sqlList.Remove(el)
}
}
m.store.Del(sql)
_ = m.store.Del(sql)
}
// DelIds deletes ids
@ -243,7 +243,7 @@ func (m *LRUCacher) delBean(tableName string, id string) {
m.idList.Remove(el)
m.clearIds(tableName)
}
m.store.Del(tid)
_ = m.store.Del(tid)
}
// DelBean deletes beans in some table
@ -265,10 +265,6 @@ type sqlNode struct {
lastVisit time.Time
}
func genSQLKey(sql string, args interface{}) string {
return fmt.Sprintf("%s-%v", sql, args)
}
func genID(prefix string, id string) string {
return fmt.Sprintf("%s-%s", prefix, id)
}

View File

@ -36,7 +36,7 @@ func (c *ContextHook) End(ctx context.Context, result sql.Result, err error) {
c.Ctx = ctx
c.Result = result
c.Err = err
c.ExecuteTime = time.Now().Sub(c.start)
c.ExecuteTime = time.Since(c.start)
}
// Hook represents a hook behaviour

View File

@ -16,11 +16,21 @@ import (
"time"
)
// ConversionFrom is an inteface to allow retrieve data from database
type ConversionFrom interface {
FromDB([]byte) error
}
// ConversionTo is an interface to allow store data to database
type ConversionTo interface {
ToDB() ([]byte, error)
}
// Conversion is an interface. A type implements Conversion will according
// the custom method to fill into database and retrieve from database.
type Conversion interface {
FromDB([]byte) error
ToDB() ([]byte, error)
ConversionFrom
ConversionTo
}
// ErrNilPtr represents an error
@ -283,11 +293,9 @@ func Assign(dest, src interface{}, originalLocation *time.Location, convertedLoc
}
}
var sv reflect.Value
switch d := dest.(type) {
case *string:
sv = reflect.ValueOf(src)
var sv = reflect.ValueOf(src)
switch sv.Kind() {
case reflect.Bool,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
@ -325,6 +333,9 @@ func AssignValue(dv reflect.Value, src interface{}) error {
if src == nil {
return nil
}
if v, ok := src.(*interface{}); ok {
return AssignValue(dv, *v)
}
if dv.Type().Implements(scannerType) {
return dv.Interface().(sql.Scanner).Scan(src)

View File

@ -35,6 +35,56 @@ func AsInt64(src interface{}) (int64, error) {
return int64(v), nil
case uint64:
return int64(v), nil
case *int:
if v == nil {
return 0, nil
}
return int64(*v), nil
case *int16:
if v == nil {
return 0, nil
}
return int64(*v), nil
case *int32:
if v == nil {
return 0, nil
}
return int64(*v), nil
case *int8:
if v == nil {
return 0, nil
}
return int64(*v), nil
case *int64:
if v == nil {
return 0, nil
}
return *v, nil
case *uint:
if v == nil {
return 0, nil
}
return int64(*v), nil
case *uint8:
if v == nil {
return 0, nil
}
return int64(*v), nil
case *uint16:
if v == nil {
return 0, nil
}
return int64(*v), nil
case *uint32:
if v == nil {
return 0, nil
}
return int64(*v), nil
case *uint64:
if v == nil {
return 0, nil
}
return int64(*v), nil
case []byte:
return strconv.ParseInt(string(v), 10, 64)
case string:
@ -110,9 +160,7 @@ func AsUint64(src interface{}) (uint64, error) {
return 0, fmt.Errorf("unsupported value %T as uint64", src)
}
var (
_ sql.Scanner = &NullUint64{}
)
var _ sql.Scanner = &NullUint64{}
// NullUint64 represents an uint64 that may be null.
// NullUint64 implements the Scanner interface so
@ -142,9 +190,7 @@ func (n NullUint64) Value() (driver.Value, error) {
return n.Uint64, nil
}
var (
_ sql.Scanner = &NullUint32{}
)
var _ sql.Scanner = &NullUint32{}
// NullUint32 represents an uint32 that may be null.
// NullUint32 implements the Scanner interface so

View File

@ -24,7 +24,10 @@ func Interface2Interface(userLocation *time.Location, v interface{}) (interface{
return vv.String, nil
case *sql.RawBytes:
if len([]byte(*vv)) > 0 {
return []byte(*vv), nil
src := []byte(*vv)
dest := make([]byte, len(src))
copy(dest, src)
return dest, nil
}
return nil, nil
case *sql.NullInt32:

View File

@ -8,12 +8,14 @@ import (
"database/sql"
"fmt"
"strconv"
"strings"
"time"
"xorm.io/xorm/internal/utils"
)
// String2Time converts a string to time with original location
// be aware for time strings (HH:mm:ss) returns zero year (LMT) for converted location
func String2Time(s string, originalLocation *time.Location, convertedLocation *time.Location) (*time.Time, error) {
if len(s) == 19 {
if s == utils.ZeroTime0 || s == utils.ZeroTime1 {
@ -26,6 +28,9 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t
dt = dt.In(convertedLocation)
return &dt, nil
} else if len(s) == 20 && s[10] == 'T' && s[19] == 'Z' {
if strings.HasPrefix(s, "0000-00-00T00:00:00") || strings.HasPrefix(s, "0001-01-01T00:00:00") {
return &time.Time{}, nil
}
dt, err := time.ParseInLocation("2006-01-02T15:04:05", s[:19], originalLocation)
if err != nil {
return nil, err
@ -33,15 +38,63 @@ func String2Time(s string, originalLocation *time.Location, convertedLocation *t
dt = dt.In(convertedLocation)
return &dt, nil
} else if len(s) == 25 && s[10] == 'T' && s[19] == '+' && s[22] == ':' {
if strings.HasPrefix(s, "0000-00-00T00:00:00") || strings.HasPrefix(s, "0001-01-01T00:00:00") {
return &time.Time{}, nil
}
dt, err := time.Parse(time.RFC3339, s)
if err != nil {
return nil, err
}
dt = dt.In(convertedLocation)
return &dt, nil
} else if len(s) >= 21 && s[10] == 'T' && s[19] == '.' {
if strings.HasPrefix(s, "0000-00-00T00:00:00."+strings.Repeat("0", len(s)-20)) ||
strings.HasPrefix(s, "0001-01-01T00:00:00."+strings.Repeat("0", len(s)-20)) {
return &time.Time{}, nil
}
dt, err := time.Parse(time.RFC3339Nano, s)
if err != nil {
return nil, err
}
dt = dt.In(convertedLocation)
return &dt, nil
} else if len(s) >= 21 && s[19] == '.' {
if strings.HasPrefix(s, "0000-00-00T00:00:00."+strings.Repeat("0", len(s)-20)) ||
strings.HasPrefix(s, "0001-01-01T00:00:00."+strings.Repeat("0", len(s)-20)) {
return &time.Time{}, nil
}
layout := "2006-01-02 15:04:05." + strings.Repeat("0", len(s)-20)
dt, err := time.ParseInLocation(layout, s, originalLocation)
if err != nil {
return nil, err
}
dt = dt.In(convertedLocation)
return &dt, nil
} else if len(s) == 10 && s[4] == '-' {
if s == "0000-00-00" || s == "0001-01-01" {
return &time.Time{}, nil
}
dt, err := time.ParseInLocation("2006-01-02", s, originalLocation)
if err != nil {
return nil, err
}
dt = dt.In(convertedLocation)
return &dt, nil
} else if len(s) == 8 && s[2] == ':' && s[5] == ':' {
dt, err := time.ParseInLocation("15:04:05", s, originalLocation)
if err != nil {
return nil, err
}
dt = dt.AddDate(2006, 01, 02).In(convertedLocation)
// back to zero year
dt = dt.AddDate(-2006, -01, -02)
return &dt, nil
} else {
i, err := strconv.ParseInt(s, 10, 64)
if err == nil {
if i == 0 {
return &time.Time{}, nil
}
tm := time.Unix(i, 0).In(convertedLocation)
return &tm, nil
}
@ -68,6 +121,9 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.
if !t.Valid {
return nil, nil
}
if utils.IsTimeZero(t.Time) {
return &time.Time{}, nil
}
z, _ := t.Time.Zone()
if len(z) == 0 || t.Time.Year() == 0 || t.Time.Location().String() != dbLoc.String() {
tm := time.Date(t.Time.Year(), t.Time.Month(), t.Time.Day(), t.Time.Hour(),
@ -77,6 +133,9 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.
tm := t.Time.In(uiLoc)
return &tm, nil
case *time.Time:
if utils.IsTimeZero(*t) {
return &time.Time{}, nil
}
z, _ := t.Zone()
if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() {
tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(),
@ -86,6 +145,9 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.
tm := t.In(uiLoc)
return &tm, nil
case time.Time:
if utils.IsTimeZero(t) {
return &time.Time{}, nil
}
z, _ := t.Zone()
if len(z) == 0 || t.Year() == 0 || t.Location().String() != dbLoc.String() {
tm := time.Date(t.Year(), t.Month(), t.Day(), t.Hour(),
@ -95,12 +157,21 @@ func AsTime(src interface{}, dbLoc *time.Location, uiLoc *time.Location) (*time.
tm := t.In(uiLoc)
return &tm, nil
case int:
if t == 0 {
return &time.Time{}, nil
}
tm := time.Unix(int64(t), 0).In(uiLoc)
return &tm, nil
case int64:
if t == 0 {
return &time.Time{}, nil
}
tm := time.Unix(t, 0).In(uiLoc)
return &tm, nil
case *sql.NullInt64:
if t.Int64 == 0 {
return &time.Time{}, nil
}
tm := time.Unix(t.Int64, 0).In(uiLoc)
return &tm, nil
}

41
convert/time_test.go Normal file
View File

@ -0,0 +1,41 @@
// Copyright 2021 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 convert
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestString2Time(t *testing.T) {
expectedLoc, err := time.LoadLocation("Asia/Shanghai")
assert.NoError(t, err)
cases := map[string]time.Time{
"2021-08-10": time.Date(2021, 8, 10, 8, 0, 0, 0, expectedLoc),
"2021-07-11 10:44:00": time.Date(2021, 7, 11, 18, 44, 0, 0, expectedLoc),
"2021-07-11 10:44:00.999": time.Date(2021, 7, 11, 18, 44, 0, 999000000, expectedLoc),
"2021-07-11 10:44:00.999999": time.Date(2021, 7, 11, 18, 44, 0, 999999000, expectedLoc),
"2021-07-11 10:44:00.999999999": time.Date(2021, 7, 11, 18, 44, 0, 999999999, expectedLoc),
"2021-06-06T22:58:20+08:00": time.Date(2021, 6, 6, 22, 58, 20, 0, expectedLoc),
"2021-06-06T22:58:20.999+08:00": time.Date(2021, 6, 6, 22, 58, 20, 999000000, expectedLoc),
"2021-06-06T22:58:20.999999+08:00": time.Date(2021, 6, 6, 22, 58, 20, 999999000, expectedLoc),
"2021-06-06T22:58:20.999999999+08:00": time.Date(2021, 6, 6, 22, 58, 20, 999999999, expectedLoc),
"2021-08-10T10:33:04Z": time.Date(2021, 8, 10, 18, 33, 0o4, 0, expectedLoc),
"2021-08-10T10:33:04.999Z": time.Date(2021, 8, 10, 18, 33, 0o4, 999000000, expectedLoc),
"2021-08-10T10:33:04.999999Z": time.Date(2021, 8, 10, 18, 33, 0o4, 999999000, expectedLoc),
"2021-08-10T10:33:04.999999999Z": time.Date(2021, 8, 10, 18, 33, 0o4, 999999999, expectedLoc),
"10:22:33": time.Date(0, 1, 1, 18, 22, 33, 0, expectedLoc),
}
for layout, tm := range cases {
t.Run(layout, func(t *testing.T) {
target, err := String2Time(layout, time.UTC, expectedLoc)
assert.NoError(t, err)
assert.EqualValues(t, tm, *target)
})
}
}

View File

@ -136,7 +136,7 @@ func (db *DB) reflectNew(typ reflect.Type) reflect.Value {
cs = &cacheStruct{reflect.MakeSlice(reflect.SliceOf(typ), DefaultCacheSize, DefaultCacheSize), 0}
db.reflectCache[typ] = cs
} else {
cs.idx = cs.idx + 1
cs.idx++
}
return cs.value.Index(cs.idx).Addr()
}

View File

@ -96,7 +96,7 @@ func BenchmarkOriQuery(b *testing.B) {
if err != nil {
b.Error(err)
}
//fmt.Println(Id, Name, Title, Age, Alias, NickName)
// fmt.Println(Id, Name, Title, Age, Alias, NickName)
}
rows.Close()
}
@ -245,13 +245,13 @@ func BenchmarkSliceInterfaceQuery(b *testing.B) {
b.Error(err)
}
b.Log(slice)
switch slice[1].(type) {
switch st := slice[1].(type) {
case *string:
if *slice[1].(*string) != "xlw" {
if *st != "xlw" {
b.Error(errors.New("name should be xlw"))
}
case []byte:
if string(slice[1].([]byte)) != "xlw" {
if string(st) != "xlw" {
b.Error(errors.New("name should be xlw"))
}
}
@ -399,14 +399,14 @@ func BenchmarkMapInterfaceQuery(b *testing.B) {
if err != nil {
b.Error(err)
}
switch m["name"].(type) {
switch t := m["name"].(type) {
case string:
if m["name"].(string) != "xlw" {
if t != "xlw" {
b.Log(m)
b.Error(errors.New("name should be xlw"))
}
case []byte:
if string(m["name"].([]byte)) != "xlw" {
if string(t) != "xlw" {
b.Log(m)
b.Error(errors.New("name should be xlw"))
}

View File

@ -62,7 +62,7 @@ func (rs *Rows) ScanStructByIndex(dest ...interface{}) error {
for _, vvv := range vvvs {
for j := 0; j < vvv.NumField(); j++ {
newDest[i] = vvv.Field(j).Addr().Interface()
i = i + 1
i++
}
}

View File

@ -93,7 +93,7 @@ func (s *Stmt) ExecContext(ctx context.Context, args ...interface{}) (sql.Result
if err != nil {
return nil, err
}
res, err := s.Stmt.ExecContext(ctx, args)
res, err := s.Stmt.ExecContext(ctx, args...)
hookCtx.End(ctx, res, err)
if err := s.db.afterProcess(hookCtx); err != nil {
return nil, err

1218
dialects/dameng.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -38,11 +38,23 @@ func (uri *URI) SetSchema(schema string) {
}
}
// enumerates all autoincr mode
const (
IncrAutoincrMode = iota
SequenceAutoincrMode
)
// DialectFeatures represents a dialect parameters
type DialectFeatures struct {
AutoincrMode int // 0 autoincrement column, 1 sequence
}
// Dialect represents a kind of database
type Dialect interface {
Init(*URI) error
URI() *URI
Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error)
Features() *DialectFeatures
SQLType(*schemas.Column) string
Alias(string) string // return what a sql type's alias of
@ -61,16 +73,18 @@ type Dialect interface {
GetTables(queryer core.Queryer, ctx context.Context) ([]*schemas.Table, error)
IsTableExist(queryer core.Queryer, ctx context.Context, tableName string) (bool, error)
CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool)
CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error)
DropTableSQL(tableName string) (string, bool)
CreateSequenceSQL(ctx context.Context, queryer core.Queryer, seqName string) (string, error)
IsSequenceExist(ctx context.Context, queryer core.Queryer, seqName string) (bool, error)
DropSequenceSQL(seqName string) (string, error)
GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error)
IsColumnExist(queryer core.Queryer, ctx context.Context, tableName string, colName string) (bool, error)
AddColumnSQL(tableName string, col *schemas.Column) string
ModifyColumnSQL(tableName string, col *schemas.Column) string
ForUpdateSQL(query string) string
Filters() []Filter
SetParams(params map[string]string)
}
@ -103,6 +117,59 @@ func (db *Base) URI() *URI {
return db.uri
}
// CreateTableSQL implements Dialect
func (db *Base) CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) {
if tableName == "" {
tableName = table.Name
}
quoter := db.dialect.Quoter()
var b strings.Builder
b.WriteString("CREATE TABLE IF NOT EXISTS ")
if err := quoter.QuoteTo(&b, tableName); err != nil {
return "", false, err
}
b.WriteString(" (")
for i, colName := range table.ColumnsSeq() {
col := table.GetColumn(colName)
s, _ := ColumnString(db.dialect, col, col.IsPrimaryKey && len(table.PrimaryKeys) == 1, false)
b.WriteString(s)
if i != len(table.ColumnsSeq())-1 {
b.WriteString(", ")
}
}
if len(table.PrimaryKeys) > 1 {
b.WriteString(", PRIMARY KEY (")
b.WriteString(quoter.Join(table.PrimaryKeys, ","))
b.WriteString(")")
}
b.WriteString(")")
return b.String(), false, nil
}
func (db *Base) CreateSequenceSQL(ctx context.Context, queryer core.Queryer, seqName string) (string, error) {
return fmt.Sprintf(`CREATE SEQUENCE %s
minvalue 1
nomaxvalue
start with 1
increment by 1
nocycle
nocache`, seqName), nil
}
func (db *Base) IsSequenceExist(ctx context.Context, queryer core.Queryer, seqName string) (bool, error) {
return false, fmt.Errorf("unsupported sequence feature")
}
func (db *Base) DropSequenceSQL(seqName string) (string, error) {
return fmt.Sprintf("DROP SEQUENCE %s", seqName), nil
}
// DropTableSQL returns drop table SQL
func (db *Base) DropTableSQL(tableName string) (string, bool) {
quote := db.dialect.Quoter().Quote
@ -140,8 +207,8 @@ func (db *Base) IsColumnExist(queryer core.Queryer, ctx context.Context, tableNa
// AddColumnSQL returns a SQL to add a column
func (db *Base) AddColumnSQL(tableName string, col *schemas.Column) string {
s, _ := ColumnString(db.dialect, col, true)
return fmt.Sprintf("ALTER TABLE %v ADD %v", db.dialect.Quoter().Quote(tableName), s)
s, _ := ColumnString(db.dialect, col, true, false)
return fmt.Sprintf("ALTER TABLE %s ADD %s", db.dialect.Quoter().Quote(tableName), s)
}
// CreateIndexSQL returns a SQL to create index
@ -172,22 +239,15 @@ func (db *Base) DropIndexSQL(tableName string, index *schemas.Index) string {
// ModifyColumnSQL returns a SQL to modify SQL
func (db *Base) ModifyColumnSQL(tableName string, col *schemas.Column) string {
s, _ := ColumnString(db.dialect, col, false)
return fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s", tableName, s)
}
// ForUpdateSQL returns for updateSQL
func (db *Base) ForUpdateSQL(query string) string {
return query + " FOR UPDATE"
s, _ := ColumnString(db.dialect, col, false, false)
return fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s", db.quoter.Quote(tableName), s)
}
// SetParams set params
func (db *Base) SetParams(params map[string]string) {
}
var (
dialects = map[string]func() Dialect{}
)
var dialects = map[string]func() Dialect{}
// RegisterDialect register database dialect
func RegisterDialect(dbName schemas.DBType, dialectFunc func() Dialect) {
@ -219,8 +279,10 @@ 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{} }},
}
for driverName, v := range providedDrvsNDialects {
@ -237,7 +299,7 @@ func init() {
}
// ColumnString generate column description string according dialect
func ColumnString(dialect Dialect, col *schemas.Column, includePrimaryKey bool) (string, error) {
func ColumnString(dialect Dialect, col *schemas.Column, includePrimaryKey, supportCollation bool) (string, error) {
bd := strings.Builder{}
if err := dialect.Quoter().QuoteTo(&bd, col.Name); err != nil {
@ -252,43 +314,50 @@ func ColumnString(dialect Dialect, col *schemas.Column, includePrimaryKey bool)
return "", err
}
if err := bd.WriteByte(' '); err != nil {
return "", err
if supportCollation && col.Collation != "" {
if _, err := bd.WriteString(" COLLATE "); err != nil {
return "", err
}
if _, err := bd.WriteString(col.Collation); err != nil {
return "", err
}
}
if includePrimaryKey && col.IsPrimaryKey {
if _, err := bd.WriteString("PRIMARY KEY "); err != nil {
if _, err := bd.WriteString(" PRIMARY KEY"); err != nil {
return "", err
}
if col.IsAutoIncrement {
if _, err := bd.WriteString(dialect.AutoIncrStr()); err != nil {
if err := bd.WriteByte(' '); err != nil {
return "", err
}
if err := bd.WriteByte(' '); err != nil {
if _, err := bd.WriteString(dialect.AutoIncrStr()); err != nil {
return "", err
}
}
}
if col.Default != "" {
if _, err := bd.WriteString("DEFAULT "); err != nil {
if !col.DefaultIsEmpty {
if _, err := bd.WriteString(" DEFAULT "); err != nil {
return "", err
}
if _, err := bd.WriteString(col.Default); err != nil {
return "", err
}
if err := bd.WriteByte(' '); err != nil {
return "", err
if col.Default == "" {
if _, err := bd.WriteString("''"); err != nil {
return "", err
}
} else {
if _, err := bd.WriteString(col.Default); err != nil {
return "", err
}
}
}
if col.Nullable {
if _, err := bd.WriteString("NULL "); err != nil {
if _, err := bd.WriteString(" NULL"); err != nil {
return "", err
}
} else {
if _, err := bd.WriteString("NOT NULL "); err != nil {
if _, err := bd.WriteString(" NOT NULL"); err != nil {
return "", err
}
}

View File

@ -5,22 +5,24 @@
package dialects
import (
"context"
"fmt"
"strconv"
"strings"
)
// Filter is an interface to filter SQL
type Filter interface {
Do(sql string) string
Do(ctx context.Context, sql string) string
}
// SeqFilter filter SQL replace ?, ? ... to $1, $2 ...
type SeqFilter struct {
// postgresSeqFilter filter SQL replace ?, ? ... to $1, $2 ...
type postgresSeqFilter struct {
Prefix string
Start int
}
func convertQuestionMark(sql, prefix string, start int) string {
func postgresSeqFilterConvertQuestionMark(sql, prefix string, start int) string {
var buf strings.Builder
var beginSingleQuote bool
var isLineComment bool
@ -28,10 +30,77 @@ func convertQuestionMark(sql, prefix string, start int) string {
var isMaybeLineComment bool
var isMaybeComment bool
var isMaybeCommentEnd bool
var index = start
var isMaybeJsonbQuestion bool
index := start
for i, c := range sql {
if !beginSingleQuote && !isLineComment && !isComment && !isMaybeJsonbQuestion && c == '?' {
buf.WriteString(fmt.Sprintf("%s%v", prefix, index))
index++
} else {
if isMaybeJsonbQuestion && c == '?' {
isMaybeJsonbQuestion = false
} else if isMaybeLineComment {
if c == '-' {
isLineComment = true
}
isMaybeLineComment = false
} else if isMaybeComment {
if c == '*' {
isComment = true
}
isMaybeComment = false
} else if isMaybeCommentEnd {
if c == '/' {
isComment = false
}
isMaybeCommentEnd = false
} else if isLineComment {
if c == '\n' {
isLineComment = false
}
} else if isComment {
if c == '*' {
isMaybeCommentEnd = true
}
} else if !beginSingleQuote && c == '-' {
isMaybeLineComment = true
} else if !beginSingleQuote && c == '/' {
isMaybeComment = true
} else if !beginSingleQuote && c == ' ' && i >= 7 && strings.TrimSpace(sql[i-7:i]) == "::jsonb" {
isMaybeJsonbQuestion = true
} else if c == '\'' {
beginSingleQuote = !beginSingleQuote
}
buf.WriteRune(c)
}
}
return buf.String()
}
// Do implements Filter
func (s *postgresSeqFilter) Do(ctx context.Context, sql string) string {
return postgresSeqFilterConvertQuestionMark(sql, s.Prefix, s.Start)
}
// oracleSeqFilter filter SQL replace ?, ? ... to :1, :2 ...
type oracleSeqFilter struct {
Prefix string
Start int
}
func oracleSeqFilterConvertQuestionMark(sql, prefix string, start int) string {
var buf strings.Builder
var beginSingleQuote bool
var isLineComment bool
var isComment bool
var isMaybeLineComment bool
var isMaybeComment bool
var isMaybeCommentEnd bool
index := start
for _, c := range sql {
if !beginSingleQuote && !isLineComment && !isComment && c == '?' {
buf.WriteString(fmt.Sprintf("%s%v", prefix, index))
buf.WriteString(prefix)
buf.WriteString(strconv.Itoa(index))
index++
} else {
if isMaybeLineComment {
@ -71,6 +140,6 @@ func convertQuestionMark(sql, prefix string, start int) string {
}
// Do implements Filter
func (s *SeqFilter) Do(sql string) string {
return convertQuestionMark(sql, s.Prefix, s.Start)
func (s *oracleSeqFilter) Do(ctx context.Context, sql string) string {
return oracleSeqFilterConvertQuestionMark(sql, s.Prefix, s.Start)
}

View File

@ -7,7 +7,7 @@ import (
)
func TestSeqFilter(t *testing.T) {
var kases = map[string]string{
kases := map[string]string{
"SELECT * FROM TABLE1 WHERE a=? AND b=?": "SELECT * FROM TABLE1 WHERE a=$1 AND b=$2",
"SELECT 1, '???', '2006-01-02 15:04:05' FROM TABLE1 WHERE a=? AND b=?": "SELECT 1, '???', '2006-01-02 15:04:05' FROM TABLE1 WHERE a=$1 AND b=$2",
"select '1''?' from issue": "select '1''?' from issue",
@ -16,12 +16,12 @@ func TestSeqFilter(t *testing.T) {
"select '1\\''?',? from issue": "select '1\\''?',$1 from issue",
}
for sql, result := range kases {
assert.EqualValues(t, result, convertQuestionMark(sql, "$", 1))
assert.EqualValues(t, result, postgresSeqFilterConvertQuestionMark(sql, "$", 1))
}
}
func TestSeqFilterLineComment(t *testing.T) {
var kases = map[string]string{
kases := map[string]string{
`SELECT *
FROM TABLE1
WHERE foo='bar'
@ -49,12 +49,12 @@ func TestSeqFilterLineComment(t *testing.T) {
AND b=$2`,
}
for sql, result := range kases {
assert.EqualValues(t, result, convertQuestionMark(sql, "$", 1))
assert.EqualValues(t, result, postgresSeqFilterConvertQuestionMark(sql, "$", 1))
}
}
func TestSeqFilterComment(t *testing.T) {
var kases = map[string]string{
kases := map[string]string{
`SELECT *
FROM TABLE1
WHERE a=? /* it's a comment */
@ -73,6 +73,17 @@ func TestSeqFilterComment(t *testing.T) {
AND b=$2`,
}
for sql, result := range kases {
assert.EqualValues(t, result, convertQuestionMark(sql, "$", 1))
assert.EqualValues(t, result, postgresSeqFilterConvertQuestionMark(sql, "$", 1))
}
}
func TestSeqFilterPostgresQuestions(t *testing.T) {
kases := map[string]string{
`SELECT '{"a":1, "b":2}'::jsonb ? 'b'
FROM table1 WHERE c = ?`: `SELECT '{"a":1, "b":2}'::jsonb ? 'b'
FROM table1 WHERE c = $1`,
}
for sql, result := range kases {
assert.EqualValues(t, result, postgresSeqFilterConvertQuestionMark(sql, "$", 1))
}
}

1089
dialects/gbase8s.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -217,6 +217,7 @@ type mssql struct {
Base
defaultVarchar string
defaultChar string
useLegacy bool
}
func (db *mssql) Init(uri *URI) error {
@ -226,10 +227,12 @@ func (db *mssql) Init(uri *URI) error {
return db.Base.Init(db, uri)
}
func (db *mssql) UseLegacyLimitOffset() bool { return db.useLegacy }
func (db *mssql) SetParams(params map[string]string) {
defaultVarchar, ok := params["DEFAULT_VARCHAR"]
if ok {
var t = strings.ToUpper(defaultVarchar)
t := strings.ToUpper(defaultVarchar)
switch t {
case "NVARCHAR", "VARCHAR":
db.defaultVarchar = t
@ -242,7 +245,7 @@ func (db *mssql) SetParams(params map[string]string) {
defaultChar, ok := params["DEFAULT_CHAR"]
if ok {
var t = strings.ToUpper(defaultChar)
t := strings.ToUpper(defaultChar)
switch t {
case "NCHAR", "CHAR":
db.defaultChar = t
@ -252,6 +255,13 @@ func (db *mssql) SetParams(params map[string]string) {
} else {
db.defaultChar = "CHAR"
}
useLegacy, ok := params["USE_LEGACY_LIMIT_OFFSET"]
if ok {
if b, _ := strconv.ParseBool(useLegacy); b {
db.useLegacy = true
}
}
}
func (db *mssql) Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) {
@ -282,10 +292,16 @@ func (db *mssql) Version(ctx context.Context, queryer core.Queryer) (*schemas.Ve
}, nil
}
func (db *mssql) Features() *DialectFeatures {
return &DialectFeatures{
AutoincrMode: IncrAutoincrMode,
}
}
func (db *mssql) SQLType(c *schemas.Column) string {
var res string
switch t := c.SQLType.Name; t {
case schemas.Bool:
case schemas.Bool, schemas.Boolean:
res = schemas.Bit
if strings.EqualFold(c.Default, "true") {
c.Default = "1"
@ -313,16 +329,16 @@ func (db *mssql) SQLType(c *schemas.Column) string {
if c.Length == 0 {
res += "(MAX)"
}
case schemas.TimeStamp:
res = schemas.DateTime
case schemas.TimeStamp, schemas.DateTime:
return "DATETIME2"
case schemas.TimeStampz:
res = "DATETIMEOFFSET"
c.Length = 7
case schemas.MediumInt, schemas.TinyInt, schemas.SmallInt, schemas.UnsignedMediumInt, schemas.UnsignedTinyInt, schemas.UnsignedSmallInt:
case schemas.MediumInt, schemas.SmallInt, schemas.UnsignedMediumInt, schemas.UnsignedTinyInt, schemas.UnsignedSmallInt:
res = schemas.Int
case schemas.Text, schemas.MediumText, schemas.TinyText, schemas.LongText, schemas.Json:
res = db.defaultVarchar + "(MAX)"
case schemas.Double:
case schemas.Double, schemas.UnsignedFloat:
res = schemas.Real
case schemas.Uuid:
res = schemas.Varchar
@ -357,17 +373,17 @@ func (db *mssql) SQLType(c *schemas.Column) string {
res = t
}
if res == schemas.Int || res == schemas.Bit || res == schemas.DateTime {
if res == schemas.Int || res == schemas.Bit {
return res
}
hasLen1 := (c.Length > 0)
hasLen2 := (c.Length2 > 0)
hasLen1 := c.Length > 0
hasLen2 := c.Length2 > 0
if hasLen2 {
res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")"
res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")"
} else if hasLen1 {
res += "(" + strconv.Itoa(c.Length) + ")"
res += "(" + strconv.FormatInt(c.Length, 10) + ")"
}
return res
}
@ -393,11 +409,11 @@ func (db *mssql) IsReserved(name string) bool {
func (db *mssql) SetQuotePolicy(quotePolicy QuotePolicy) {
switch quotePolicy {
case QuotePolicyNone:
var q = mssqlQuoter
q := mssqlQuoter
q.IsReserved = schemas.AlwaysNoReserve
db.quoter = q
case QuotePolicyReserved:
var q = mssqlQuoter
q := mssqlQuoter
q.IsReserved = db.IsReserved
db.quoter = q
case QuotePolicyAlways:
@ -418,8 +434,8 @@ func (db *mssql) DropTableSQL(tableName string) (string, bool) {
}
func (db *mssql) ModifyColumnSQL(tableName string, col *schemas.Column) string {
s, _ := ColumnString(db.dialect, col, false)
return fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s", tableName, s)
s, _ := ColumnString(db.dialect, col, false, true)
return fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s", db.quoter.Quote(tableName), s)
}
func (db *mssql) IndexCheckSQL(tableName, idxName string) (string, []interface{}) {
@ -444,7 +460,7 @@ func (db *mssql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
s := `select a.name as name, b.name as ctype,a.max_length,a.precision,a.scale,a.is_nullable as nullable,
"default_is_null" = (CASE WHEN c.text is null THEN 1 ELSE 0 END),
replace(replace(isnull(c.text,''),'(',''),')','') as vdefault,
ISNULL(p.is_primary_key, 0), a.is_identity as is_identity
ISNULL(p.is_primary_key, 0), a.is_identity as is_identity, a.collation_name
from sys.columns a
left join sys.types b on a.user_type_id=b.user_type_id
left join sys.syscomments c on a.default_object_id=c.id
@ -465,9 +481,10 @@ func (db *mssql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
colSeq := make([]string, 0)
for rows.Next() {
var name, ctype, vdefault string
var maxLen, precision, scale int
var collation *string
var maxLen, precision, scale int64
var nullable, isPK, defaultIsNull, isIncrement bool
err = rows.Scan(&name, &ctype, &maxLen, &precision, &scale, &nullable, &defaultIsNull, &vdefault, &isPK, &isIncrement)
err = rows.Scan(&name, &ctype, &maxLen, &precision, &scale, &nullable, &defaultIsNull, &vdefault, &isPK, &isIncrement, &collation)
if err != nil {
return nil, nil, err
}
@ -489,6 +506,9 @@ func (db *mssql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
} else {
col.Length = maxLen
}
if collation != nil {
col.Collation = *collation
}
switch ct {
case "DATETIMEOFFSET":
col.SQLType = schemas.SQLType{Name: schemas.TimeStampz, DefaultLength: 0, DefaultLength2: 0}
@ -498,6 +518,12 @@ func (db *mssql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
col.Length /= 2
col.Length2 /= 2
}
case "DATETIME2":
col.SQLType = schemas.SQLType{Name: schemas.DateTime, DefaultLength: 7, DefaultLength2: 0}
col.Length = scale
case "DATETIME":
col.SQLType = schemas.SQLType{Name: schemas.DateTime, DefaultLength: 3, DefaultLength2: 0}
col.Length = scale
case "IMAGE":
col.SQLType = schemas.SQLType{Name: schemas.VarBinary, DefaultLength: 0, DefaultLength2: 0}
case "NCHAR":
@ -556,10 +582,10 @@ func (db *mssql) GetIndexes(queryer core.Queryer, ctx context.Context, tableName
IXS.NAME AS [INDEX_NAME],
C.NAME AS [COLUMN_NAME],
IXS.is_unique AS [IS_UNIQUE]
FROM SYS.INDEXES IXS
INNER JOIN SYS.INDEX_COLUMNS IXCS
FROM sys.indexes IXS
INNER JOIN sys.index_columns IXCS
ON IXS.OBJECT_ID=IXCS.OBJECT_ID AND IXS.INDEX_ID = IXCS.INDEX_ID
INNER JOIN SYS.COLUMNS C ON IXS.OBJECT_ID=C.OBJECT_ID
INNER JOIN sys.columns C ON IXS.OBJECT_ID=C.OBJECT_ID
AND IXCS.COLUMN_ID=C.COLUMN_ID
WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =?
`
@ -615,39 +641,38 @@ WHERE IXS.TYPE_DESC='NONCLUSTERED' and OBJECT_NAME(IXS.OBJECT_ID) =?
return indexes, nil
}
func (db *mssql) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) {
var sql string
func (db *mssql) CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) {
if tableName == "" {
tableName = table.Name
}
sql = "IF NOT EXISTS (SELECT [name] FROM sys.tables WHERE [name] = '" + tableName + "' ) CREATE TABLE "
quoter := db.dialect.Quoter()
var b strings.Builder
b.WriteString("IF NOT EXISTS (SELECT [name] FROM sys.tables WHERE [name] = '")
quoter.QuoteTo(&b, tableName)
b.WriteString("' ) CREATE TABLE ")
quoter.QuoteTo(&b, tableName)
b.WriteString(" (")
sql += db.Quoter().Quote(tableName) + " ("
pkList := table.PrimaryKeys
for _, colName := range table.ColumnsSeq() {
for i, colName := range table.ColumnsSeq() {
col := table.GetColumn(colName)
s, _ := ColumnString(db, col, col.IsPrimaryKey && len(pkList) == 1)
sql += s
sql = strings.TrimSpace(sql)
sql += ", "
s, _ := ColumnString(db.dialect, col, col.IsPrimaryKey && len(table.PrimaryKeys) == 1, true)
b.WriteString(s)
if i != len(table.ColumnsSeq())-1 {
b.WriteString(", ")
}
}
if len(pkList) > 1 {
sql += "PRIMARY KEY ( "
sql += strings.Join(pkList, ",")
sql += " ), "
if len(table.PrimaryKeys) > 1 {
b.WriteString(", PRIMARY KEY (")
b.WriteString(quoter.Join(table.PrimaryKeys, ","))
b.WriteString(")")
}
sql = sql[:len(sql)-2] + ")"
sql += ";"
return []string{sql}, true
}
b.WriteString(")")
func (db *mssql) ForUpdateSQL(query string) string {
return query
return b.String(), true, nil
}
func (db *mssql) Filters() []Filter {

View File

@ -6,7 +6,6 @@ package dialects
import (
"context"
"crypto/tls"
"database/sql"
"errors"
"fmt"
@ -39,6 +38,7 @@ var (
"CALL": true,
"CASCADE": true,
"CASE": true,
"CHAIN": true,
"CHANGE": true,
"CHAR": true,
"CHARACTER": true,
@ -129,6 +129,7 @@ var (
"OUT": true, "OUTER": true, "OUTFILE": true,
"PRECISION": true, "PRIMARY": true, "PROCEDURE": true,
"PURGE": true, "RAID0": true, "RANGE": true,
"RANK": true,
"READ": true, "READS": true, "REAL": true,
"REFERENCES": true, "REGEXP": true, "RELEASE": true,
"RENAME": true, "REPEAT": true, "REPLACE": true,
@ -172,16 +173,7 @@ var (
type mysql struct {
Base
net string
addr string
params map[string]string
loc *time.Location
timeout time.Duration
tls *tls.Config
allowAllFiles bool
allowOldPasswords bool
clientFoundRows bool
rowFormat string
rowFormat string
}
func (db *mysql) Init(uri *URI) error {
@ -189,11 +181,9 @@ func (db *mysql) Init(uri *URI) error {
return db.Base.Init(db, uri)
}
var (
mysqlColAliases = map[string]string{
"numeric": "decimal",
}
)
var mysqlColAliases = map[string]string{
"numeric": "decimal",
}
// Alias returns a alias of column
func (db *mysql) Alias(col string) string {
@ -244,10 +234,16 @@ func (db *mysql) Version(ctx context.Context, queryer core.Queryer) (*schemas.Ve
}, nil
}
func (db *mysql) Features() *DialectFeatures {
return &DialectFeatures{
AutoincrMode: IncrAutoincrMode,
}
}
func (db *mysql) SetParams(params map[string]string) {
rowFormat, ok := params["rowFormat"]
if ok {
var t = strings.ToUpper(rowFormat)
t := strings.ToUpper(rowFormat)
switch t {
case "COMPACT":
fallthrough
@ -323,12 +319,15 @@ func (db *mysql) SQLType(c *schemas.Column) string {
case schemas.UnsignedTinyInt:
res = schemas.TinyInt
isUnsigned = true
case schemas.UnsignedFloat:
res = schemas.Float
isUnsigned = true
default:
res = t
}
hasLen1 := (c.Length > 0)
hasLen2 := (c.Length2 > 0)
hasLen1 := c.Length > 0
hasLen2 := c.Length2 > 0
if res == schemas.BigInt && !hasLen1 && !hasLen2 {
c.Length = 20
@ -336,9 +335,9 @@ func (db *mysql) SQLType(c *schemas.Column) string {
}
if hasLen2 {
res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")"
res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")"
} else if hasLen1 {
res += "(" + strconv.Itoa(c.Length) + ")"
res += "(" + strconv.FormatInt(c.Length, 10) + ")"
}
if isUnsigned {
@ -386,12 +385,30 @@ func (db *mysql) IsTableExist(queryer core.Queryer, ctx context.Context, tableNa
func (db *mysql) AddColumnSQL(tableName string, col *schemas.Column) string {
quoter := db.dialect.Quoter()
s, _ := ColumnString(db, col, true)
sql := fmt.Sprintf("ALTER TABLE %v ADD %v", quoter.Quote(tableName), s)
s, _ := ColumnString(db, col, true, true)
var b strings.Builder
b.WriteString("ALTER TABLE ")
quoter.QuoteTo(&b, tableName)
b.WriteString(" ADD ")
b.WriteString(s)
if len(col.Comment) > 0 {
sql += " COMMENT '" + col.Comment + "'"
b.WriteString(" COMMENT '")
b.WriteString(col.Comment)
b.WriteString("'")
}
return sql
return b.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)
}
return fmt.Sprintf("ALTER TABLE %s MODIFY COLUMN %s", db.quoter.Quote(tableName), s)
}
func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName string) ([]string, map[string]*schemas.Column, error) {
@ -403,10 +420,10 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
"(SUBSTRING_INDEX(SUBSTRING(VERSION(), 4), '.', 1) = 2 && " +
"SUBSTRING_INDEX(SUBSTRING(VERSION(), 6), '-', 1) >= 7)))))"
s := "SELECT `COLUMN_NAME`, `IS_NULLABLE`, `COLUMN_DEFAULT`, `COLUMN_TYPE`," +
" `COLUMN_KEY`, `EXTRA`, `COLUMN_COMMENT`, " +
alreadyQuoted + " AS NEEDS_QUOTE " +
" `COLUMN_KEY`, `EXTRA`, `COLUMN_COMMENT`, `CHARACTER_MAXIMUM_LENGTH`, " +
alreadyQuoted + " AS NEEDS_QUOTE, `COLLATION_NAME` " +
"FROM `INFORMATION_SCHEMA`.`COLUMNS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?" +
" ORDER BY `COLUMNS`.ORDINAL_POSITION"
" ORDER BY `COLUMNS`.ORDINAL_POSITION ASC"
rows, err := queryer.QueryContext(ctx, s, args...)
if err != nil {
@ -422,8 +439,8 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
var columnName, nullableStr, colType, colKey, extra, comment string
var alreadyQuoted, isUnsigned bool
var colDefault *string
err = rows.Scan(&columnName, &nullableStr, &colDefault, &colType, &colKey, &extra, &comment, &alreadyQuoted)
var colDefault, maxLength, collation *string
err = rows.Scan(&columnName, &nullableStr, &colDefault, &colType, &colKey, &extra, &comment, &maxLength, &alreadyQuoted, &collation)
if err != nil {
return nil, nil, err
}
@ -439,6 +456,9 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
} else {
col.DefaultIsEmpty = true
}
if collation != nil {
col.Collation = *collation
}
fields := strings.Fields(colType)
if len(fields) == 2 && fields[1] == "unsigned" {
@ -450,7 +470,7 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
// Remove the /* mariadb-5.3 */ suffix from coltypes
colName = strings.TrimSuffix(colName, "/* mariadb-5.3 */")
colType = strings.ToUpper(colName)
var len1, len2 int
var len1, len2 int64
if len(cts) == 2 {
idx := strings.Index(cts[1], ")")
if colType == schemas.Enum && cts[1][0] == '\'' { // enum
@ -471,35 +491,42 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
}
} else {
lens := strings.Split(cts[1][0:idx], ",")
len1, err = strconv.Atoi(strings.TrimSpace(lens[0]))
len1, err = strconv.ParseInt(strings.TrimSpace(lens[0]), 10, 64)
if err != nil {
return nil, nil, err
}
if len(lens) == 2 {
len2, err = strconv.Atoi(lens[1])
len2, err = strconv.ParseInt(lens[1], 10, 64)
if err != nil {
return nil, nil, err
}
}
}
} else {
switch colType {
case "MEDIUMTEXT", "LONGTEXT", "TEXT":
len1, err = strconv.ParseInt(*maxLength, 10, 64)
if err != nil {
return nil, nil, err
}
}
}
if isUnsigned {
colType = "UNSIGNED " + colType
}
col.Length = len1
col.Length2 = len2
if _, ok := schemas.SqlTypes[colType]; ok {
col.SQLType = schemas.SQLType{Name: colType, DefaultLength: len1, DefaultLength2: len2}
} else {
return nil, nil, fmt.Errorf("Unknown colType %v", colType)
if _, ok := schemas.SqlTypes[colType]; !ok {
return nil, nil, fmt.Errorf("unknown colType %v", colType)
}
col.SQLType = schemas.SQLType{Name: colType, DefaultLength: len1, DefaultLength2: len2}
if colKey == "PRI" {
col.IsPrimaryKey = true
}
if colKey == "UNI" {
// col.is
}
// if colKey == "UNI" {
// col.is
// }
if extra == "auto_increment" {
col.IsAutoIncrement = true
@ -523,7 +550,7 @@ func (db *mysql) GetColumns(queryer core.Queryer, ctx context.Context, tableName
func (db *mysql) GetTables(queryer core.Queryer, ctx context.Context) ([]*schemas.Table, error) {
args := []interface{}{db.uri.DBName}
s := "SELECT `TABLE_NAME`, `ENGINE`, `AUTO_INCREMENT`, `TABLE_COMMENT` from " +
s := "SELECT `TABLE_NAME`, `ENGINE`, `AUTO_INCREMENT`, `TABLE_COMMENT`, `TABLE_COLLATION` from " +
"`INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_SCHEMA`=? AND (`ENGINE`='MyISAM' OR `ENGINE` = 'InnoDB' OR `ENGINE` = 'TokuDB')"
rows, err := queryer.QueryContext(ctx, s, args...)
@ -535,9 +562,9 @@ func (db *mysql) GetTables(queryer core.Queryer, ctx context.Context) ([]*schema
tables := make([]*schemas.Table, 0)
for rows.Next() {
table := schemas.NewEmptyTable()
var name, engine string
var name, engine, collation string
var autoIncr, comment *string
err = rows.Scan(&name, &engine, &autoIncr, &comment)
err = rows.Scan(&name, &engine, &autoIncr, &comment, &collation)
if err != nil {
return nil, err
}
@ -547,6 +574,7 @@ func (db *mysql) GetTables(queryer core.Queryer, ctx context.Context) ([]*schema
table.Comment = *comment
}
table.StoreEngine = engine
table.Collation = collation
tables = append(tables, table)
}
if rows.Err() != nil {
@ -558,11 +586,11 @@ func (db *mysql) GetTables(queryer core.Queryer, ctx context.Context) ([]*schema
func (db *mysql) SetQuotePolicy(quotePolicy QuotePolicy) {
switch quotePolicy {
case QuotePolicyNone:
var q = mysqlQuoter
q := mysqlQuoter
q.IsReserved = schemas.AlwaysNoReserve
db.quoter = q
case QuotePolicyReserved:
var q = mysqlQuoter
q := mysqlQuoter
q.IsReserved = db.IsReserved
db.quoter = q
case QuotePolicyAlways:
@ -574,7 +602,7 @@ func (db *mysql) SetQuotePolicy(quotePolicy QuotePolicy) {
func (db *mysql) GetIndexes(queryer core.Queryer, ctx context.Context, tableName string) (map[string]*schemas.Index, error) {
args := []interface{}{db.uri.DBName, tableName}
s := "SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?"
s := "SELECT `INDEX_NAME`, `NON_UNIQUE`, `COLUMN_NAME` FROM `INFORMATION_SCHEMA`.`STATISTICS` WHERE `TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? ORDER BY `SEQ_IN_INDEX`"
rows, err := queryer.QueryContext(ctx, s, args...)
if err != nil {
@ -625,57 +653,67 @@ func (db *mysql) GetIndexes(queryer core.Queryer, ctx context.Context, tableName
return indexes, nil
}
func (db *mysql) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) {
var sql = "CREATE TABLE IF NOT EXISTS "
func (db *mysql) CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) {
if tableName == "" {
tableName = table.Name
}
quoter := db.Quoter()
quoter := db.dialect.Quoter()
var b strings.Builder
b.WriteString("CREATE TABLE IF NOT EXISTS ")
quoter.QuoteTo(&b, tableName)
b.WriteString(" (")
sql += quoter.Quote(tableName)
sql += " ("
for i, colName := range table.ColumnsSeq() {
col := table.GetColumn(colName)
s, _ := ColumnString(db.dialect, col, col.IsPrimaryKey && len(table.PrimaryKeys) == 1, true)
b.WriteString(s)
if len(table.ColumnsSeq()) > 0 {
pkList := table.PrimaryKeys
for _, colName := range table.ColumnsSeq() {
col := table.GetColumn(colName)
s, _ := ColumnString(db, col, col.IsPrimaryKey && len(pkList) == 1)
sql += s
sql = strings.TrimSpace(sql)
if len(col.Comment) > 0 {
sql += " COMMENT '" + col.Comment + "'"
}
sql += ", "
if len(col.Comment) > 0 {
b.WriteString(" COMMENT '")
b.WriteString(col.Comment)
b.WriteString("'")
}
if len(pkList) > 1 {
sql += "PRIMARY KEY ( "
sql += quoter.Join(pkList, ",")
sql += " ), "
if i != len(table.ColumnsSeq())-1 {
b.WriteString(", ")
}
sql = sql[:len(sql)-2]
}
sql += ")"
if len(table.PrimaryKeys) > 1 {
b.WriteString(", PRIMARY KEY (")
b.WriteString(quoter.Join(table.PrimaryKeys, ","))
b.WriteString(")")
}
b.WriteString(")")
if table.StoreEngine != "" {
sql += " ENGINE=" + table.StoreEngine
b.WriteString(" ENGINE=")
b.WriteString(table.StoreEngine)
}
var charset = table.Charset
charset := table.Charset
if len(charset) == 0 {
charset = db.URI().Charset
}
if len(charset) != 0 {
sql += " DEFAULT CHARSET " + charset
b.WriteString(" DEFAULT CHARSET ")
b.WriteString(charset)
}
if db.rowFormat != "" {
sql += " ROW_FORMAT=" + db.rowFormat
b.WriteString(" ROW_FORMAT=")
b.WriteString(db.rowFormat)
}
return []string{sql}, true
if table.Comment != "" {
b.WriteString(" COMMENT='")
b.WriteString(table.Comment)
b.WriteString("'")
}
return b.String(), true, nil
}
func (db *mysql) Filters() []Filter {
@ -726,8 +764,9 @@ func (p *mysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) {
}
func (p *mysqlDriver) GenScanResult(colType string) (interface{}, error) {
colType = strings.Replace(colType, "UNSIGNED ", "", -1)
switch colType {
case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT", "ENUM", "SET":
case "CHAR", "VARCHAR", "TINYTEXT", "TEXT", "MEDIUMTEXT", "LONGTEXT", "ENUM", "SET", "JSON":
var s sql.NullString
return &s, nil
case "BIGINT":
@ -769,7 +808,7 @@ func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) {
// Parse protocol part of URI
p := strings.SplitN(pd[0], ":", 2)
if len(p) != 2 {
return nil, errors.New("Wrong protocol part of URI")
return nil, errors.New("wrong protocol part of URI")
}
uri.Proto = p[0]
options := strings.Split(p[1], ",")
@ -792,7 +831,7 @@ func (p *mymysqlDriver) Parse(driverName, dataSourceName string) (*URI, error) {
}
uri.Timeout = to
default:
return nil, errors.New("Unknown option: " + k)
return nil, errors.New("unknown option: " + k)
}
}
// Remove protocol part

View File

@ -509,6 +509,7 @@ var (
type oracle struct {
Base
useLegacy bool
}
func (db *oracle) Init(uri *URI) error {
@ -516,6 +517,17 @@ func (db *oracle) Init(uri *URI) error {
return db.Base.Init(db, uri)
}
func (db *oracle) UseLegacyLimitOffset() bool { return db.useLegacy }
func (db *oracle) SetParams(params map[string]string) {
useLegacy, ok := params["USE_LEGACY_LIMIT_OFFSET"]
if ok {
if b, _ := strconv.ParseBool(useLegacy); b {
db.useLegacy = true
}
}
}
func (db *oracle) Version(ctx context.Context, queryer core.Queryer) (*schemas.Version, error) {
rows, err := queryer.QueryContext(ctx, "select * from v$version where banner like 'Oracle%'")
if err != nil {
@ -539,10 +551,23 @@ func (db *oracle) Version(ctx context.Context, queryer core.Queryer) (*schemas.V
}, nil
}
func (db *oracle) Features() *DialectFeatures {
return &DialectFeatures{
AutoincrMode: SequenceAutoincrMode,
}
}
func (db *oracle) SQLType(c *schemas.Column) string {
var res string
switch t := c.SQLType.Name; t {
case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt, schemas.Bool, schemas.Serial, schemas.BigSerial:
case schemas.Bool:
if c.Default == "true" {
c.Default = "1"
} else if c.Default == "false" {
c.Default = "0"
}
res = "NUMBER(1,0)"
case schemas.Bit, schemas.TinyInt, schemas.SmallInt, schemas.MediumInt, schemas.Int, schemas.Integer, schemas.BigInt, schemas.Serial, schemas.BigSerial:
res = "NUMBER"
case schemas.Binary, schemas.VarBinary, schemas.Blob, schemas.TinyBlob, schemas.MediumBlob, schemas.LongBlob, schemas.Bytea:
return schemas.Blob
@ -560,13 +585,13 @@ func (db *oracle) SQLType(c *schemas.Column) string {
res = t
}
hasLen1 := (c.Length > 0)
hasLen2 := (c.Length2 > 0)
hasLen1 := c.Length > 0
hasLen2 := c.Length2 > 0
if hasLen2 {
res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")"
res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")"
} else if hasLen1 {
res += "(" + strconv.Itoa(c.Length) + ")"
res += "(" + strconv.FormatInt(c.Length, 10) + ")"
}
return res
}
@ -596,11 +621,11 @@ func (db *oracle) IsReserved(name string) bool {
}
func (db *oracle) DropTableSQL(tableName string) (string, bool) {
return fmt.Sprintf("DROP TABLE `%s`", tableName), false
return fmt.Sprintf("DROP TABLE \"%s\"", tableName), false
}
func (db *oracle) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) {
var sql = "CREATE TABLE "
func (db *oracle) CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) {
sql := "CREATE TABLE "
if tableName == "" {
tableName = table.Name
}
@ -615,7 +640,7 @@ func (db *oracle) CreateTableSQL(table *schemas.Table, tableName string) ([]stri
/*if col.IsPrimaryKey && len(pkList) == 1 {
sql += col.String(b.dialect)
} else {*/
s, _ := ColumnString(db, col, false)
s, _ := ColumnString(db, col, false, false)
sql += s
// }
sql = strings.TrimSpace(sql)
@ -629,17 +654,21 @@ func (db *oracle) CreateTableSQL(table *schemas.Table, tableName string) ([]stri
}
sql = sql[:len(sql)-2] + ")"
return []string{sql}, false
return sql, false, nil
}
func (db *oracle) IsSequenceExist(ctx context.Context, queryer core.Queryer, seqName string) (bool, error) {
return db.HasRecords(queryer, ctx, `SELECT sequence_name FROM user_sequences WHERE sequence_name = :1`, seqName)
}
func (db *oracle) SetQuotePolicy(quotePolicy QuotePolicy) {
switch quotePolicy {
case QuotePolicyNone:
var q = oracleQuoter
q := oracleQuoter
q.IsReserved = schemas.AlwaysNoReserve
db.quoter = q
case QuotePolicyReserved:
var q = oracleQuoter
q := oracleQuoter
q.IsReserved = db.IsReserved
db.quoter = q
case QuotePolicyAlways:
@ -655,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)
}
@ -684,7 +724,7 @@ func (db *oracle) GetColumns(queryer core.Queryer, ctx context.Context, tableNam
col.Indexes = make(map[string]int)
var colName, colDefault, nullable, dataType, dataPrecision, dataScale *string
var dataLen int
var dataLen int64
err = rows.Scan(&colName, &colDefault, &dataType, &dataLen, &dataPrecision,
&dataScale, &nullable)
@ -707,16 +747,16 @@ func (db *oracle) GetColumns(queryer core.Queryer, ctx context.Context, tableNam
var ignore bool
var dt string
var len1, len2 int
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.Atoi(lens[0])
len2, _ = strconv.Atoi(lens[1])
len1, _ = strconv.ParseInt(lens[0], 10, 64)
len2, _ = strconv.ParseInt(lens[1], 10, 64)
} else {
len1, _ = strconv.Atoi(lens[0])
len1, _ = strconv.ParseInt(lens[0], 10, 64)
}
}
@ -846,7 +886,7 @@ func (db *oracle) GetIndexes(queryer core.Queryer, ctx context.Context, tableNam
func (db *oracle) Filters() []Filter {
return []Filter{
&SeqFilter{Prefix: ":", Start: 1},
&oracleSeqFilter{Prefix: ":", Start: 1},
}
}
@ -926,3 +966,7 @@ func (o *oci8Driver) Parse(driverName, dataSourceName string) (*URI, error) {
}
return db, nil
}
type oracleDriver struct {
godrorDriver
}

View File

@ -821,6 +821,8 @@ func (db *postgres) Version(ctx context.Context, queryer core.Queryer) (*schemas
}
// Postgres: 9.5.22 on x86_64-pc-linux-gnu (Debian 9.5.22-1.pgdg90+1), compiled by gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516, 64-bit
// Postgres: PostgreSQL 15.3, compiled by Visual C++ build 1914, 64-bit
// KingbaseES V008R006C008B0014 on x64, compiled by Visual C++ build 1800, 64-bit
// CockroachDB CCL v19.2.4 (x86_64-unknown-linux-gnu, built
if strings.HasPrefix(version, "CockroachDB") {
versions := strings.Split(strings.TrimPrefix(version, "CockroachDB CCL "), " ")
@ -829,12 +831,37 @@ func (db *postgres) Version(ctx context.Context, queryer core.Queryer) (*schemas
Edition: "CockroachDB",
}, nil
} else if strings.HasPrefix(version, "PostgreSQL") {
versions := strings.Split(strings.TrimPrefix(version, "PostgreSQL "), " on ")
return &schemas.Version{
Number: versions[0],
Level: versions[1],
Edition: "PostgreSQL",
}, nil
if strings.Contains(version, " on ") {
versions := strings.Split(strings.TrimPrefix(version, "PostgreSQL "), " on ")
return &schemas.Version{
Number: versions[0],
Level: versions[1],
Edition: "PostgreSQL",
}, nil
} else {
versions := strings.Split(strings.TrimPrefix(version, "PostgreSQL "), ",")
return &schemas.Version{
Number: versions[0],
Level: versions[1],
Edition: "PostgreSQL",
}, nil
}
} else if strings.HasPrefix(version, "KingbaseES") {
if strings.Contains(version, " on ") {
versions := strings.Split(strings.TrimPrefix(version, "KingbaseES "), " on ")
return &schemas.Version{
Number: versions[0],
Level: versions[1],
Edition: "KingbaseES",
}, nil
} else {
versions := strings.Split(strings.TrimPrefix(version, "KingbaseES "), ",")
return &schemas.Version{
Number: versions[0],
Level: versions[1],
Edition: "KingbaseES",
}, nil
}
}
return nil, errors.New("unknow database version")
@ -862,11 +889,11 @@ func (db *postgres) needQuote(name string) bool {
func (db *postgres) SetQuotePolicy(quotePolicy QuotePolicy) {
switch quotePolicy {
case QuotePolicyNone:
var q = postgresQuoter
q := postgresQuoter
q.IsReserved = schemas.AlwaysNoReserve
db.quoter = q
case QuotePolicyReserved:
var q = postgresQuoter
q := postgresQuoter
q.IsReserved = db.needQuote
db.quoter = q
case QuotePolicyAlways:
@ -917,7 +944,7 @@ func (db *postgres) SQLType(c *schemas.Column) string {
return schemas.Uuid
case schemas.Blob, schemas.TinyBlob, schemas.MediumBlob, schemas.LongBlob:
return schemas.Bytea
case schemas.Double:
case schemas.Double, schemas.UnsignedFloat:
return "DOUBLE PRECISION"
default:
if c.IsAutoIncrement {
@ -930,17 +957,23 @@ func (db *postgres) SQLType(c *schemas.Column) string {
// for bool, we don't need length information
return res
}
hasLen1 := (c.Length > 0)
hasLen2 := (c.Length2 > 0)
hasLen1 := c.Length > 0
hasLen2 := c.Length2 > 0
if hasLen2 {
res += "(" + strconv.Itoa(c.Length) + "," + strconv.Itoa(c.Length2) + ")"
res += "(" + strconv.FormatInt(c.Length, 10) + "," + strconv.FormatInt(c.Length2, 10) + ")"
} else if hasLen1 {
res += "(" + strconv.Itoa(c.Length) + ")"
res += "(" + strconv.FormatInt(c.Length, 10) + ")"
}
return res
}
func (db *postgres) Features() *DialectFeatures {
return &DialectFeatures{
AutoincrMode: IncrAutoincrMode,
}
}
func (db *postgres) ColumnTypeKind(t string) int {
switch strings.ToUpper(t) {
case "DATETIME", "TIMESTAMP":
@ -965,41 +998,6 @@ func (db *postgres) AutoIncrStr() string {
return ""
}
func (db *postgres) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) {
var sql string
sql = "CREATE TABLE IF NOT EXISTS "
if tableName == "" {
tableName = table.Name
}
quoter := db.Quoter()
sql += quoter.Quote(tableName)
sql += " ("
if len(table.ColumnsSeq()) > 0 {
pkList := table.PrimaryKeys
for _, colName := range table.ColumnsSeq() {
col := table.GetColumn(colName)
s, _ := ColumnString(db, col, col.IsPrimaryKey && len(pkList) == 1)
sql += s
sql = strings.TrimSpace(sql)
sql += ", "
}
if len(pkList) > 1 {
sql += "PRIMARY KEY ( "
sql += quoter.Join(pkList, ",")
sql += " ), "
}
sql = sql[:len(sql)-2]
}
sql += ")"
return []string{sql}, true
}
func (db *postgres) IndexCheckSQL(tableName, idxName string) (string, []interface{}) {
if len(db.getSchema()) == 0 {
args := []interface{}{tableName, idxName}
@ -1020,13 +1018,37 @@ func (db *postgres) IsTableExist(queryer core.Queryer, ctx context.Context, tabl
db.getSchema(), tableName)
}
func (db *postgres) ModifyColumnSQL(tableName string, col *schemas.Column) string {
func (db *postgres) AddColumnSQL(tableName string, col *schemas.Column) string {
s, _ := ColumnString(db.dialect, col, true, false)
quoter := db.dialect.Quoter()
addColumnSQL := ""
commentSQL := "; "
if len(db.getSchema()) == 0 || strings.Contains(tableName, ".") {
return fmt.Sprintf("alter table %s ALTER COLUMN %s TYPE %s",
tableName, col.Name, db.SQLType(col))
addColumnSQL = fmt.Sprintf("ALTER TABLE %s ADD %s", quoter.Quote(tableName), s)
commentSQL += fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'", quoter.Quote(tableName), quoter.Quote(col.Name), col.Comment)
return addColumnSQL + commentSQL
}
return fmt.Sprintf("alter table %s.%s ALTER COLUMN %s TYPE %s",
db.getSchema(), tableName, col.Name, db.SQLType(col))
addColumnSQL = fmt.Sprintf("ALTER TABLE %s.%s ADD %s", quoter.Quote(db.getSchema()), quoter.Quote(tableName), s)
commentSQL += fmt.Sprintf("COMMENT ON COLUMN %s.%s.%s IS '%s'", quoter.Quote(db.getSchema()), quoter.Quote(tableName), quoter.Quote(col.Name), col.Comment)
return addColumnSQL + commentSQL
}
func (db *postgres) ModifyColumnSQL(tableName string, col *schemas.Column) string {
quoter := db.dialect.Quoter()
modifyColumnSQL := ""
commentSQL := "; "
if len(db.getSchema()) == 0 || strings.Contains(tableName, ".") {
modifyColumnSQL = fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s TYPE %s", quoter.Quote(tableName), quoter.Quote(col.Name), db.SQLType(col))
commentSQL += fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'", quoter.Quote(tableName), quoter.Quote(col.Name), col.Comment)
return modifyColumnSQL + commentSQL
}
modifyColumnSQL = fmt.Sprintf("ALTER TABLE %s.%s ALTER COLUMN %s TYPE %s", quoter.Quote(db.getSchema()), quoter.Quote(tableName), quoter.Quote(col.Name), db.SQLType(col))
commentSQL += fmt.Sprintf("COMMENT ON COLUMN %s.%s.%s IS '%s'", quoter.Quote(db.getSchema()), quoter.Quote(tableName), quoter.Quote(col.Name), col.Comment)
return modifyColumnSQL + commentSQL
}
func (db *postgres) DropIndexSQL(tableName string, index *schemas.Index) string {
@ -1035,11 +1057,10 @@ func (db *postgres) DropIndexSQL(tableName string, index *schemas.Index) string
tableParts := strings.Split(strings.Replace(tableName, `"`, "", -1), ".")
tableName = tableParts[len(tableParts)-1]
if !strings.HasPrefix(idxName, "UQE_") &&
!strings.HasPrefix(idxName, "IDX_") {
if index.Type == schemas.UniqueType {
if index.IsRegular {
if index.Type == schemas.UniqueType && !strings.HasPrefix(idxName, "UQE_") {
idxName = fmt.Sprintf("UQE_%v_%v", tableName, index.Name)
} else {
} else if index.Type == schemas.IndexType && !strings.HasPrefix(idxName, "IDX_") {
idxName = fmt.Sprintf("IDX_%v_%v", tableName, index.Name)
}
}
@ -1084,7 +1105,7 @@ FROM pg_attribute f
LEFT JOIN pg_constraint p ON p.conrelid = c.oid AND f.attnum = ANY (p.conkey)
LEFT JOIN pg_class AS g ON p.confrelid = g.oid
LEFT JOIN INFORMATION_SCHEMA.COLUMNS s ON s.column_name=f.attname AND c.relname=s.table_name
WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.attnum;`
WHERE n.nspname= s.table_schema AND c.relkind = 'r' AND c.relname = $1%s AND f.attnum > 0 ORDER BY f.attnum;`
schema := db.getSchema()
if schema != "" {
@ -1115,9 +1136,9 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A
return nil, nil, err
}
var maxLen int
var maxLen int64
if maxLenStr != nil {
maxLen, err = strconv.Atoi(*maxLenStr)
maxLen, err = strconv.ParseInt(*maxLenStr, 10, 64)
if err != nil {
return nil, nil, err
}
@ -1130,7 +1151,7 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A
col.Name = strings.Trim(colName, `" `)
if colDefault != nil {
var theDefault = *colDefault
theDefault := *colDefault
// cockroach has type with the default value with :::
// and postgres with ::, we should remove them before store them
idx := strings.Index(theDefault, ":::")
@ -1164,7 +1185,7 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A
col.IsPrimaryKey = true
}
col.Nullable = (isNullable == "YES")
col.Nullable = isNullable == "YES"
switch strings.ToLower(dataType) {
case "character varying", "string":
@ -1191,7 +1212,7 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A
startIdx := strings.Index(strings.ToLower(dataType), "string(")
if startIdx != -1 && strings.HasSuffix(dataType, ")") {
length := dataType[startIdx+8 : len(dataType)-1]
l, _ := strconv.Atoi(length)
l, _ := strconv.ParseInt(length, 10, 64)
col.SQLType = schemas.SQLType{Name: "STRING", DefaultLength: l, DefaultLength2: 0}
} else {
col.SQLType = schemas.SQLType{Name: strings.ToUpper(dataType), DefaultLength: 0, DefaultLength2: 0}
@ -1211,9 +1232,7 @@ WHERE n.nspname= s.table_schema AND c.relkind = 'r'::char AND c.relname = $1%s A
col.Default = "'" + col.Default + "'"
}
} else if col.SQLType.IsTime() {
if strings.HasSuffix(col.Default, "::timestamp without time zone") {
col.Default = strings.TrimSuffix(col.Default, "::timestamp without time zone")
}
col.Default = strings.TrimSuffix(col.Default, "::timestamp without time zone")
}
}
cols[col.Name] = col
@ -1274,7 +1293,7 @@ func (db *postgres) GetIndexes(queryer core.Queryer, ctx context.Context, tableN
s := "SELECT indexname, indexdef FROM pg_indexes WHERE tablename=$1"
if len(db.getSchema()) != 0 {
args = append(args, db.getSchema())
s = s + " AND schemaname=$2"
s += " AND schemaname=$2"
}
rows, err := queryer.QueryContext(ctx, s, args...)
@ -1307,6 +1326,12 @@ func (db *postgres) GetIndexes(queryer core.Queryer, ctx context.Context, tableN
indexType = schemas.IndexType
}
colNames = getIndexColName(indexdef)
// Oid It's a special index. You can't put it in. TODO: This is not perfect.
if indexName == tableName+"_oid_index" && len(colNames) == 1 && colNames[0] == "oid" {
continue
}
var isRegular bool
if strings.HasPrefix(indexName, "IDX_"+tableName) || strings.HasPrefix(indexName, "UQE_"+tableName) {
newIdxName := indexName[5+len(tableName):]
@ -1331,8 +1356,36 @@ func (db *postgres) GetIndexes(queryer core.Queryer, ctx context.Context, tableN
return indexes, nil
}
func (db *postgres) CreateTableSQL(ctx context.Context, queryer core.Queryer, table *schemas.Table, tableName string) (string, bool, error) {
quoter := db.dialect.Quoter()
if len(db.getSchema()) != 0 && !strings.Contains(tableName, ".") {
tableName = fmt.Sprintf("%s.%s", db.getSchema(), tableName)
}
createTableSQL, ok, err := db.Base.CreateTableSQL(ctx, queryer, table, tableName)
if err != nil {
return "", ok, err
}
commentSQL := "; "
if table.Comment != "" {
// support schema.table -> "schema"."table"
commentSQL += fmt.Sprintf("COMMENT ON TABLE %s IS '%s'; ", quoter.Quote(tableName), table.Comment)
}
for _, colName := range table.ColumnsSeq() {
col := table.GetColumn(colName)
if len(col.Comment) > 0 {
commentSQL += fmt.Sprintf("COMMENT ON COLUMN %s.%s IS '%s'; ", quoter.Quote(tableName), quoter.Quote(col.Name), col.Comment)
}
}
return createTableSQL + commentSQL, true, nil
}
func (db *postgres) Filters() []Filter {
return []Filter{&SeqFilter{Prefix: "$", Start: 1}}
return []Filter{&postgresSeqFilter{Prefix: "$", Start: 1}}
}
type pqDriver struct {

View File

@ -11,6 +11,7 @@ import (
"fmt"
"regexp"
"strings"
"unicode"
"xorm.io/xorm/core"
"xorm.io/xorm/schemas"
@ -184,14 +185,20 @@ func (db *sqlite3) Version(ctx context.Context, queryer core.Queryer) (*schemas.
}, nil
}
func (db *sqlite3) Features() *DialectFeatures {
return &DialectFeatures{
AutoincrMode: IncrAutoincrMode,
}
}
func (db *sqlite3) SetQuotePolicy(quotePolicy QuotePolicy) {
switch quotePolicy {
case QuotePolicyNone:
var q = sqlite3Quoter
q := sqlite3Quoter
q.IsReserved = schemas.AlwaysNoReserve
db.quoter = q
case QuotePolicyReserved:
var q = sqlite3Quoter
q := sqlite3Quoter
q.IsReserved = db.IsReserved
db.quoter = q
case QuotePolicyAlways:
@ -285,45 +292,6 @@ func (db *sqlite3) DropIndexSQL(tableName string, index *schemas.Index) string {
return fmt.Sprintf("DROP INDEX %v", db.Quoter().Quote(idxName))
}
func (db *sqlite3) CreateTableSQL(table *schemas.Table, tableName string) ([]string, bool) {
var sql string
sql = "CREATE TABLE IF NOT EXISTS "
if tableName == "" {
tableName = table.Name
}
quoter := db.Quoter()
sql += quoter.Quote(tableName)
sql += " ("
if len(table.ColumnsSeq()) > 0 {
pkList := table.PrimaryKeys
for _, colName := range table.ColumnsSeq() {
col := table.GetColumn(colName)
s, _ := ColumnString(db, col, col.IsPrimaryKey && len(pkList) == 1)
sql += s
sql = strings.TrimSpace(sql)
sql += ", "
}
if len(pkList) > 1 {
sql += "PRIMARY KEY ( "
sql += quoter.Join(pkList, ",")
sql += " ), "
}
sql = sql[:len(sql)-2]
}
sql += ")"
return []string{sql}, true
}
func (db *sqlite3) ForUpdateSQL(query string) string {
return query
}
func (db *sqlite3) IsColumnExist(queryer core.Queryer, ctx context.Context, tableName, colName string) (bool, error) {
query := "SELECT * FROM " + tableName + " LIMIT 0"
rows, err := queryer.QueryContext(ctx, query)
@ -349,11 +317,11 @@ func (db *sqlite3) IsColumnExist(queryer core.Queryer, ctx context.Context, tabl
// splitColStr splits a sqlite col strings as fields
func splitColStr(colStr string) []string {
colStr = strings.TrimSpace(colStr)
var results = make([]string, 0, 10)
results := make([]string, 0, 10)
var lastIdx int
var hasC, hasQuote bool
for i, c := range colStr {
if c == ' ' && !hasQuote {
if unicode.IsSpace(c) && !hasQuote {
if hasC {
results = append(results, colStr[lastIdx:i])
hasC = false
@ -383,7 +351,7 @@ func parseString(colStr string) (*schemas.Column, error) {
for idx, field := range fields {
if idx == 0 {
col.Name = strings.Trim(strings.Trim(field, "`[] "), `"`)
col.Name = strings.Trim(strings.TrimSpace(field), "`[]'\"")
continue
} else if idx == 1 {
col.SQLType = schemas.SQLType{Name: field, DefaultLength: 0, DefaultLength2: 0}
@ -433,6 +401,8 @@ func (db *sqlite3) GetColumns(queryer core.Queryer, ctx context.Context, tableNa
return nil, nil, errors.New("no table named " + tableName)
}
name = strings.ReplaceAll(name, "\n", " ")
nStart := strings.Index(name, "(")
nEnd := strings.LastIndex(name, ")")
reg := regexp.MustCompile(`[^\(,\)]*(\([^\(]*\))?`)
@ -516,7 +486,7 @@ func (db *sqlite3) GetIndexes(queryer core.Queryer, ctx context.Context, tableNa
if !tmpSQL.Valid {
continue
}
sql := tmpSQL.String
sql := strings.ReplaceAll(tmpSQL.String, "\n", " ")
index := new(schemas.Index)
nNStart := strings.Index(sql, "INDEX")

View File

@ -11,7 +11,7 @@ import (
)
func TestSplitColStr(t *testing.T) {
var kases = []struct {
kases := []struct {
colStr string
fields []string
}{
@ -27,6 +27,13 @@ func TestSplitColStr(t *testing.T) {
"`created`", "DATETIME", "DEFAULT", "'2006-01-02 15:04:05'", "NULL",
},
},
{
colStr: ` id INTEGER not null
primary key autoincrement`,
fields: []string{
"id", "INTEGER", "not", "null", "primary", "key", "autoincrement",
},
},
}
for _, kase := range kases {

View File

@ -11,14 +11,14 @@ import (
"xorm.io/xorm/internal/utils"
"xorm.io/xorm/names"
"xorm.io/xorm/schemas"
)
// TableNameWithSchema will add schema prefix on table name if possible
func TableNameWithSchema(dialect Dialect, tableName string) string {
// Add schema name as prefix of table name.
// Only for postgres database.
if dialect.URI().Schema != "" &&
strings.Index(tableName, ".") == -1 {
if dialect.URI().Schema != "" && !strings.Contains(tableName, ".") {
return fmt.Sprintf("%s.%s", dialect.URI().Schema, tableName)
}
return tableName
@ -27,20 +27,21 @@ func TableNameWithSchema(dialect Dialect, tableName string) string {
// TableNameNoSchema returns table name with given tableName
func TableNameNoSchema(dialect Dialect, mapper names.Mapper, tableName interface{}) string {
quote := dialect.Quoter().Quote
switch tableName.(type) {
switch tt := tableName.(type) {
case []string:
t := tableName.([]string)
if len(t) > 1 {
return fmt.Sprintf("%v AS %v", quote(t[0]), quote(t[1]))
} else if len(t) == 1 {
return quote(t[0])
if len(tt) > 1 {
if dialect.URI().DBType == schemas.ORACLE {
return fmt.Sprintf("%v %v", quote(tt[0]), quote(tt[1]))
}
return fmt.Sprintf("%v AS %v", quote(tt[0]), quote(tt[1]))
} else if len(tt) == 1 {
return quote(tt[0])
}
case []interface{}:
t := tableName.([]interface{})
l := len(t)
l := len(tt)
var table string
if l > 0 {
f := t[0]
f := tt[0]
switch f.(type) {
case string:
table = f.(string)
@ -57,7 +58,10 @@ func TableNameNoSchema(dialect Dialect, mapper names.Mapper, tableName interface
}
}
if l > 1 {
return fmt.Sprintf("%v AS %v", quote(table), quote(fmt.Sprintf("%v", t[1])))
if dialect.URI().DBType == schemas.ORACLE {
return fmt.Sprintf("%v %v", quote(table), quote(fmt.Sprintf("%v", tt[1])))
}
return fmt.Sprintf("%v AS %v", quote(table), quote(fmt.Sprintf("%v", tt[1])))
} else if l == 1 {
return quote(table)
}

View File

@ -5,50 +5,62 @@
package dialects
import (
"strings"
"time"
"xorm.io/xorm/internal/utils"
"xorm.io/xorm/schemas"
)
// FormatTime format time as column type
func FormatTime(dialect Dialect, sqlTypeName string, t time.Time) (v interface{}) {
switch sqlTypeName {
case schemas.Time:
s := t.Format("2006-01-02 15:04:05") // time.RFC3339
v = s[11:19]
case schemas.Date:
v = t.Format("2006-01-02")
case schemas.DateTime, schemas.TimeStamp, schemas.Varchar: // !DarthPestilane! format time when sqlTypeName is schemas.Varchar.
if dialect.URI().DBType == schemas.ORACLE {
v = t
} else {
v = t.Format("2006-01-02 15:04:05")
// FormatColumnTime format column time
func FormatColumnTime(dialect Dialect, dbLocation *time.Location, col *schemas.Column, t time.Time) (interface{}, error) {
if utils.IsTimeZero(t) {
if col.Nullable {
return nil, nil
}
if col.SQLType.IsNumeric() {
return 0, nil
}
if col.SQLType.Name == schemas.TimeStamp || col.SQLType.Name == schemas.TimeStampz {
t = time.Unix(0, 0)
}
}
tmZone := dbLocation
if col.TimeZone != nil {
tmZone = col.TimeZone
}
t = t.In(tmZone)
switch col.SQLType.Name {
case schemas.Date:
return t.Format("2006-01-02"), nil
case schemas.Time:
layout := "15:04:05"
if col.Length > 0 {
// we can use int(...) casting here as it's very unlikely to a huge sized field
layout += "." + strings.Repeat("0", int(col.Length))
}
return t.Format(layout), nil
case schemas.DateTime, schemas.TimeStamp:
layout := "2006-01-02 15:04:05"
if col.Length > 0 {
// we can use int(...) casting here as it's very unlikely to a huge sized field
layout += "." + strings.Repeat("0", int(col.Length))
}
return t.Format(layout), nil
case schemas.Varchar:
return t.Format("2006-01-02 15:04:05"), nil
case schemas.TimeStampz:
if dialect.URI().DBType == schemas.MSSQL {
v = t.Format("2006-01-02T15:04:05.9999999Z07:00")
return t.Format("2006-01-02T15:04:05.9999999Z07:00"), nil
} else {
v = t.Format(time.RFC3339Nano)
return t.Format(time.RFC3339Nano), nil
}
case schemas.BigInt, schemas.Int:
v = t.Unix()
return t.Unix(), nil
default:
v = t
return t, nil
}
return
}
// FormatColumnTime format column time
func FormatColumnTime(dialect Dialect, defaultTimeZone *time.Location, col *schemas.Column, t time.Time) (v interface{}) {
if t.IsZero() {
if col.Nullable {
return nil
}
return ""
}
if col.TimeZone != nil {
return FormatTime(dialect, col.SQLType.Name, t.In(col.TimeZone))
}
return FormatTime(dialect, col.SQLType.Name, t.In(defaultTimeZone))
}

190
dialects/time_test.go Normal file
View File

@ -0,0 +1,190 @@
// Copyright 2019 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 (
"testing"
"time"
"github.com/stretchr/testify/assert"
"xorm.io/xorm/schemas"
)
type dialect struct {
Dialect
dbType schemas.DBType
}
func (d dialect) URI() *URI {
return &URI{
DBType: d.dbType,
}
}
func TestFormatColumnTime(t *testing.T) {
date := time.Date(2020, 10, 23, 10, 14, 15, 123456, time.Local)
tests := []struct {
name string
dialect Dialect
location *time.Location
column *schemas.Column
time time.Time
wantRes interface{}
wantErr error
}{
{
name: "nullable",
dialect: nil,
location: nil,
column: &schemas.Column{Nullable: true},
time: time.Time{},
wantRes: nil,
wantErr: nil,
},
{
name: "invalid sqltype",
dialect: nil,
location: nil,
column: &schemas.Column{SQLType: schemas.SQLType{Name: schemas.Bit}},
time: time.Time{},
wantRes: 0,
wantErr: nil,
},
{
name: "return default",
dialect: nil,
location: date.Location(),
column: &schemas.Column{SQLType: schemas.SQLType{Name: schemas.Bit}},
time: date,
wantRes: date,
wantErr: nil,
},
{
name: "return default (set timezone)",
dialect: nil,
location: date.Location(),
column: &schemas.Column{SQLType: schemas.SQLType{Name: schemas.Bit}, TimeZone: time.UTC},
time: date,
wantRes: date.In(time.UTC),
wantErr: nil,
},
{
name: "format date",
dialect: nil,
location: date.Location(),
column: &schemas.Column{SQLType: schemas.SQLType{Name: schemas.Date}},
time: date,
wantRes: date.Format("2006-01-02"),
wantErr: nil,
},
{
name: "format time",
dialect: nil,
location: date.Location(),
column: &schemas.Column{SQLType: schemas.SQLType{Name: schemas.Time}},
time: date,
wantRes: date.Format("15:04:05"),
wantErr: nil,
},
{
name: "format time (set length)",
dialect: nil,
location: date.Location(),
column: &schemas.Column{SQLType: schemas.SQLType{Name: schemas.Time}, Length: 64},
time: date,
wantRes: date.Format("15:04:05.999999999"),
wantErr: nil,
},
{
name: "format datetime",
dialect: nil,
location: date.Location(),
column: &schemas.Column{SQLType: schemas.SQLType{Name: schemas.DateTime}},
time: date,
wantRes: date.Format("2006-01-02 15:04:05"),
wantErr: nil,
},
{
name: "format datetime (set length)",
dialect: nil,
location: date.Location(),
column: &schemas.Column{SQLType: schemas.SQLType{Name: schemas.DateTime}, Length: 64},
time: date,
wantRes: date.Format("2006-01-02 15:04:05.999999999"),
wantErr: nil,
},
{
name: "format timestamp",
dialect: nil,
location: date.Location(),
column: &schemas.Column{SQLType: schemas.SQLType{Name: schemas.TimeStamp}},
time: date,
wantRes: date.Format("2006-01-02 15:04:05"),
wantErr: nil,
},
{
name: "format timestamp (set length)",
dialect: nil,
location: date.Location(),
column: &schemas.Column{SQLType: schemas.SQLType{Name: schemas.TimeStamp}, Length: 64},
time: date,
wantRes: date.Format("2006-01-02 15:04:05.999999999"),
wantErr: nil,
},
{
name: "format varchar",
dialect: nil,
location: date.Location(),
column: &schemas.Column{SQLType: schemas.SQLType{Name: schemas.Varchar}},
time: date,
wantRes: date.Format("2006-01-02 15:04:05"),
wantErr: nil,
},
{
name: "format timestampz",
dialect: dialect{},
location: date.Location(),
column: &schemas.Column{SQLType: schemas.SQLType{Name: schemas.TimeStampz}},
time: date,
wantRes: date.Format(time.RFC3339Nano),
wantErr: nil,
},
{
name: "format timestampz (mssql)",
dialect: dialect{dbType: schemas.MSSQL},
location: date.Location(),
column: &schemas.Column{SQLType: schemas.SQLType{Name: schemas.TimeStampz}},
time: date,
wantRes: date.Format("2006-01-02T15:04:05.9999999Z07:00"),
wantErr: nil,
},
{
name: "format int",
dialect: nil,
location: date.Location(),
column: &schemas.Column{SQLType: schemas.SQLType{Name: schemas.Int}},
time: date,
wantRes: date.Unix(),
wantErr: nil,
},
{
name: "format bigint",
dialect: nil,
location: date.Location(),
column: &schemas.Column{SQLType: schemas.SQLType{Name: schemas.BigInt}},
time: date,
wantRes: date.Unix(),
wantErr: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := FormatColumnTime(tt.dialect, tt.location, tt.column, tt.time)
assert.Equal(t, tt.wantErr, err)
assert.Equal(t, tt.wantRes, got)
})
}
}

243
doc.go
View File

@ -3,231 +3,246 @@
// license that can be found in the LICENSE file.
/*
Package xorm is a simple and powerful ORM for Go.
Installation
# Installation
Make sure you have installed Go 1.11+ and then:
go get xorm.io/xorm
go get xorm.io/xorm
Create Engine
# Create Engine
Firstly, we should create an engine for a database
engine, err := xorm.NewEngine(driverName, dataSourceName)
engine, err := xorm.NewEngine(driverName, dataSourceName)
Method NewEngine's parameters are the same as sql.Open which depend drivers' implementation.
Generally, one engine for an application is enough. You can define it as a package variable.
Raw Methods
# Raw Methods
XORM supports raw SQL execution:
1. query with a SQL string, the returned results is []map[string][]byte
results, err := engine.Query("select * from user")
results, err := engine.Query("select * from user")
2. query with a SQL string, the returned results is []map[string]string
results, err := engine.QueryString("select * from user")
results, err := engine.QueryString("select * from user")
3. query with a SQL string, the returned results is []map[string]interface{}
results, err := engine.QueryInterface("select * from user")
results, err := engine.QueryInterface("select * from user")
4. execute with a SQL string, the returned results
affected, err := engine.Exec("update user set .... where ...")
affected, err := engine.Exec("update user set .... where ...")
ORM Methods
# ORM Methods
There are 8 major ORM methods and many helpful methods to use to operate database.
1. Insert one or multiple records to database
affected, err := engine.Insert(&struct)
// INSERT INTO struct () values ()
affected, err := engine.Insert(&struct1, &struct2)
// INSERT INTO struct1 () values ()
// INSERT INTO struct2 () values ()
affected, err := engine.Insert(&sliceOfStruct)
// INSERT INTO struct () values (),(),()
affected, err := engine.Insert(&struct1, &sliceOfStruct2)
// INSERT INTO struct1 () values ()
// INSERT INTO struct2 () values (),(),()
affected, err := engine.Insert(&struct)
// INSERT INTO struct () values ()
affected, err := engine.Insert(&struct1, &struct2)
// INSERT INTO struct1 () values ()
// INSERT INTO struct2 () values ()
affected, err := engine.Insert(&sliceOfStruct)
// INSERT INTO struct () values (),(),()
affected, err := engine.Insert(&struct1, &sliceOfStruct2)
// INSERT INTO struct1 () values ()
// INSERT INTO struct2 () values (),(),()
2. Query one record or one variable from database
has, err := engine.Get(&user)
// SELECT * FROM user LIMIT 1
has, err := engine.Get(&user)
// SELECT * FROM user LIMIT 1
var id int64
has, err := engine.Table("user").Where("name = ?", name).Get(&id)
// SELECT id FROM user WHERE name = ? LIMIT 1
var id int64
has, err := engine.Table("user").Where("name = ?", name).Get(&id)
// SELECT id FROM user WHERE name = ? LIMIT 1
var id int64
var name string
has, err := engine.Table(&user).Cols("id", "name").Get(&id, &name)
// SELECT id, name FROM user LIMIT 1
3. Query multiple records from database
var sliceOfStructs []Struct
err := engine.Find(&sliceOfStructs)
// SELECT * FROM user
var sliceOfStructs []Struct
err := engine.Find(&sliceOfStructs)
// SELECT * FROM user
var mapOfStructs = make(map[int64]Struct)
err := engine.Find(&mapOfStructs)
// SELECT * FROM user
var mapOfStructs = make(map[int64]Struct)
err := engine.Find(&mapOfStructs)
// SELECT * FROM user
var int64s []int64
err := engine.Table("user").Cols("id").Find(&int64s)
// SELECT id FROM user
var int64s []int64
err := engine.Table("user").Cols("id").Find(&int64s)
// SELECT id FROM user
4. Query multiple records and record by record handle, there two methods, one is Iterate,
another is Rows
err := engine.Iterate(new(User), func(i int, bean interface{}) error {
// do something
})
// SELECT * FROM user
err := engine.Iterate(new(User), func(i int, bean interface{}) error {
// do something
})
// SELECT * FROM user
rows, err := engine.Rows(...)
// SELECT * FROM user
defer rows.Close()
bean := new(Struct)
for rows.Next() {
err = rows.Scan(bean)
}
rows, err := engine.Rows(...)
// SELECT * FROM user
defer rows.Close()
bean := new(Struct)
for rows.Next() {
err = rows.Scan(bean)
}
or
rows, err := engine.Cols("name", "age").Rows(...)
// SELECT * FROM user
defer rows.Close()
for rows.Next() {
var name string
var age int
err = rows.Scan(&name, &age)
}
5. Update one or more records
affected, err := engine.ID(...).Update(&user)
// UPDATE user SET ...
affected, err := engine.ID(...).Update(&user)
// UPDATE user SET ...
6. Delete one or more records, Delete MUST has condition
affected, err := engine.Where(...).Delete(&user)
// DELETE FROM user Where ...
affected, err := engine.Where(...).Delete(&user)
// DELETE FROM user Where ...
7. Count records
counts, err := engine.Count(&user)
// SELECT count(*) AS total FROM user
counts, err := engine.Count(&user)
// SELECT count(*) AS total FROM user
counts, err := engine.SQL("select count(*) FROM user").Count()
// select count(*) FROM user
counts, err := engine.SQL("select count(*) FROM user").Count()
// select count(*) FROM user
8. Sum records
sumFloat64, err := engine.Sum(&user, "id")
// SELECT sum(id) from user
sumFloat64, err := engine.Sum(&user, "id")
// SELECT sum(id) from user
sumFloat64s, err := engine.Sums(&user, "id1", "id2")
// SELECT sum(id1), sum(id2) from user
sumFloat64s, err := engine.Sums(&user, "id1", "id2")
// SELECT sum(id1), sum(id2) from user
sumInt64s, err := engine.SumsInt(&user, "id1", "id2")
// SELECT sum(id1), sum(id2) from user
sumInt64s, err := engine.SumsInt(&user, "id1", "id2")
// SELECT sum(id1), sum(id2) from user
Conditions
# Conditions
The above 8 methods could use with condition methods chainable.
Notice: the above 8 methods should be the last chainable method.
1. ID, In
engine.ID(1).Get(&user) // for single primary key
// SELECT * FROM user WHERE id = 1
engine.ID(schemas.PK{1, 2}).Get(&user) // for composite primary keys
// SELECT * FROM user WHERE id1 = 1 AND id2 = 2
engine.In("id", 1, 2, 3).Find(&users)
// SELECT * FROM user WHERE id IN (1, 2, 3)
engine.In("id", []int{1, 2, 3}).Find(&users)
// SELECT * FROM user WHERE id IN (1, 2, 3)
engine.ID(1).Get(&user) // for single primary key
// SELECT * FROM user WHERE id = 1
engine.ID(schemas.PK{1, 2}).Get(&user) // for composite primary keys
// SELECT * FROM user WHERE id1 = 1 AND id2 = 2
engine.In("id", 1, 2, 3).Find(&users)
// SELECT * FROM user WHERE id IN (1, 2, 3)
engine.In("id", []int{1, 2, 3}).Find(&users)
// SELECT * FROM user WHERE id IN (1, 2, 3)
2. Where, And, Or
engine.Where().And().Or().Find()
// SELECT * FROM user WHERE (.. AND ..) OR ...
engine.Where().And().Or().Find()
// SELECT * FROM user WHERE (.. AND ..) OR ...
3. OrderBy, Asc, Desc
engine.Asc().Desc().Find()
// SELECT * FROM user ORDER BY .. ASC, .. DESC
engine.OrderBy().Find()
// SELECT * FROM user ORDER BY ..
engine.Asc().Desc().Find()
// SELECT * FROM user ORDER BY .. ASC, .. DESC
engine.OrderBy().Find()
// SELECT * FROM user ORDER BY ..
4. Limit, Top
engine.Limit().Find()
// SELECT * FROM user LIMIT .. OFFSET ..
engine.Top(5).Find()
// SELECT TOP 5 * FROM user // for mssql
// SELECT * FROM user LIMIT .. OFFSET 0 //for other databases
engine.Limit().Find()
// SELECT * FROM user LIMIT .. OFFSET ..
engine.Top(5).Find()
// SELECT TOP 5 * FROM user // for mssql
// SELECT * FROM user LIMIT .. OFFSET 0 //for other databases
5. SQL, let you custom SQL
var users []User
engine.SQL("select * from user").Find(&users)
var users []User
engine.SQL("select * from user").Find(&users)
6. Cols, Omit, Distinct
var users []*User
engine.Cols("col1, col2").Find(&users)
// SELECT col1, col2 FROM user
engine.Cols("col1", "col2").Where().Update(user)
// UPDATE user set col1 = ?, col2 = ? Where ...
engine.Omit("col1").Find(&users)
// SELECT col2, col3 FROM user
engine.Omit("col1").Insert(&user)
// INSERT INTO table (non-col1) VALUES ()
engine.Distinct("col1").Find(&users)
// SELECT DISTINCT col1 FROM user
var users []*User
engine.Cols("col1, col2").Find(&users)
// SELECT col1, col2 FROM user
engine.Cols("col1", "col2").Where().Update(user)
// UPDATE user set col1 = ?, col2 = ? Where ...
engine.Omit("col1").Find(&users)
// SELECT col2, col3 FROM user
engine.Omit("col1").Insert(&user)
// INSERT INTO table (non-col1) VALUES ()
engine.Distinct("col1").Find(&users)
// SELECT DISTINCT col1 FROM user
7. Join, GroupBy, Having
engine.GroupBy("name").Having("name='xlw'").Find(&users)
//SELECT * FROM user GROUP BY name HAVING name='xlw'
engine.Join("LEFT", "userdetail", "user.id=userdetail.id").Find(&users)
//SELECT * FROM user LEFT JOIN userdetail ON user.id=userdetail.id
engine.GroupBy("name").Having("name='xlw'").Find(&users)
//SELECT * FROM user GROUP BY name HAVING name='xlw'
engine.Join("LEFT", "userdetail", "user.id=userdetail.id").Find(&users)
//SELECT * FROM user LEFT JOIN userdetail ON user.id=userdetail.id
Builder
# Builder
xorm could work with xorm.io/builder directly.
1. With Where
var cond = builder.Eq{"a":1, "b":2}
engine.Where(cond).Find(&users)
var cond = builder.Eq{"a":1, "b":2}
engine.Where(cond).Find(&users)
2. With In
var subQuery = builder.Select("name").From("group")
engine.In("group_name", subQuery).Find(&users)
var subQuery = builder.Select("name").From("group")
engine.In("group_name", subQuery).Find(&users)
3. With Join
var subQuery = builder.Select("name").From("group")
engine.Join("INNER", subQuery, "group.id = user.group_id").Find(&users)
var subQuery = builder.Select("name").From("group")
engine.Join("INNER", subQuery, "group.id = user.group_id").Find(&users)
4. With SetExprs
var subQuery = builder.Select("name").From("group")
engine.ID(1).SetExprs("name", subQuery).Update(new(User))
var subQuery = builder.Select("name").From("group")
engine.ID(1).SetExprs("name", subQuery).Update(new(User))
5. With SQL
var query = builder.Select("name").From("group")
results, err := engine.SQL(query).Find(&groups)
var query = builder.Select("name").From("group")
results, err := engine.SQL(query).Find(&groups)
6. With Query
var query = builder.Select("name").From("group")
results, err := engine.Query(query)
results, err := engine.QueryString(query)
results, err := engine.QueryInterface(query)
var query = builder.Select("name").From("group")
results, err := engine.Query(query)
results, err := engine.QueryString(query)
results, err := engine.QueryInterface(query)
7. With Exec
var query = builder.Insert("a, b").Into("table1").Select("b, c").From("table2")
results, err := engine.Exec(query)
var query = builder.Insert("a, b").Into("table1").Select("b, c").From("table2")
results, err := engine.Exec(query)
More usage, please visit http://xorm.io/docs
*/

495
engine.go
View File

@ -7,12 +7,13 @@ package xorm
import (
"context"
"database/sql"
"errors"
"fmt"
"io"
"os"
"reflect"
"regexp"
"runtime"
"strconv"
"strings"
"time"
@ -50,7 +51,7 @@ type Engine struct {
// NewEngine new a db manager according to the parameter. Currently support four
// drivers
func NewEngine(driverName string, dataSourceName string) (*Engine, error) {
func NewEngine(driverName string, dataSourceName string, driverOptions ...func(db *sql.DB) error) (*Engine, error) {
dialect, err := dialects.OpenDialect(driverName, dataSourceName)
if err != nil {
return nil, err
@ -61,6 +62,12 @@ func NewEngine(driverName string, dataSourceName string) (*Engine, error) {
return nil, err
}
for _, driverOption := range driverOptions {
if err := driverOption(db.DB); err != nil {
return nil, err
}
}
return newEngine(driverName, dataSourceName, dialect, db)
}
@ -248,16 +255,16 @@ func (engine *Engine) SQLType(c *schemas.Column) string {
return engine.dialect.SQLType(c)
}
// AutoIncrStr Database's autoincrement statement
func (engine *Engine) AutoIncrStr() string {
return engine.dialect.AutoIncrStr()
}
// SetConnMaxLifetime sets the maximum amount of time a connection may be reused.
func (engine *Engine) SetConnMaxLifetime(d time.Duration) {
engine.DB().SetConnMaxLifetime(d)
}
// SetConnMaxIdleTime sets the maximum amount of time a connection may be idle.
func (engine *Engine) SetConnMaxIdleTime(d time.Duration) {
engine.DB().SetConnMaxIdleTime(d)
}
// SetMaxOpenConns is only available for go 1.2+
func (engine *Engine) SetMaxOpenConns(conns int) {
engine.DB().SetMaxOpenConns(conns)
@ -334,7 +341,7 @@ func (engine *Engine) Ping() error {
// SQL method let's you manually write raw SQL and operate
// For example:
//
// engine.SQL("select * from user").Find(&users)
// engine.SQL("select * from user").Find(&users)
//
// This code will execute "select * from user" and set the records to users
func (engine *Engine) SQL(query interface{}, args ...interface{}) *Session {
@ -359,15 +366,15 @@ func (engine *Engine) NoAutoCondition(no ...bool) *Session {
return session.NoAutoCondition(no...)
}
func (engine *Engine) loadTableInfo(table *schemas.Table) error {
colSeq, cols, err := engine.dialect.GetColumns(engine.db, engine.defaultContext, table.Name)
func (engine *Engine) loadTableInfo(ctx context.Context, table *schemas.Table) error {
colSeq, cols, err := engine.dialect.GetColumns(engine.db, ctx, table.Name)
if err != nil {
return err
}
for _, name := range colSeq {
table.AddColumn(cols[name])
}
indexes, err := engine.dialect.GetIndexes(engine.db, engine.defaultContext, table.Name)
indexes, err := engine.dialect.GetIndexes(engine.db, ctx, table.Name)
if err != nil {
return err
}
@ -384,7 +391,7 @@ func (engine *Engine) loadTableInfo(table *schemas.Table) error {
seq = 0
}
}
var colName = strings.Trim(parts[0], `"`)
colName := strings.Trim(parts[0], `"`)
if col := table.GetColumn(colName); col != nil {
col.Indexes[index.Name] = index.Type
} else {
@ -403,7 +410,7 @@ func (engine *Engine) DBMetas() ([]*schemas.Table, error) {
}
for _, table := range tables {
if err = engine.loadTableInfo(table); err != nil {
if err = engine.loadTableInfo(engine.defaultContext, table); err != nil {
return nil, err
}
}
@ -441,23 +448,23 @@ func (engine *Engine) DumpTablesToFile(tables []*schemas.Table, fp string, tp ..
// DumpTables dump specify tables to io.Writer
func (engine *Engine) DumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error {
return engine.dumpTables(tables, w, tp...)
return engine.dumpTables(context.Background(), tables, w, tp...)
}
func formatBool(s string, dstDialect dialects.Dialect) string {
if dstDialect.URI().DBType == schemas.MSSQL {
switch s {
case "true":
func formatBool(s bool, dstDialect dialects.Dialect) string {
if dstDialect.URI().DBType != schemas.POSTGRES {
if s {
return "1"
case "false":
return "0"
}
return "0"
}
return s
return strconv.FormatBool(s)
}
var controlCharactersRe = regexp.MustCompile(`[\x00-\x1f\x7f]+`)
// dumpTables dump database all table structs and data to w with specify db type
func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error {
func (engine *Engine) dumpTables(ctx context.Context, tables []*schemas.Table, w io.Writer, tp ...schemas.DBType) error {
var dstDialect dialects.Dialect
if len(tp) == 0 {
dstDialect = engine.dialect
@ -471,8 +478,14 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch
destURI := dialects.URI{
DBType: tp[0],
DBName: uri.DBName,
// DO NOT SET SCHEMA HERE
}
if tp[0] == schemas.POSTGRES {
destURI.Schema = engine.dialect.URI().Schema
}
if err := dstDialect.Init(&destURI); err != nil {
return err
}
dstDialect.Init(&destURI)
}
cacherMgr := caches.NewManager()
dstTableCache := tags.NewParser("xorm", dstDialect, engine.GetTableMapper(), engine.GetColumnMapper(), cacherMgr)
@ -483,6 +496,13 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch
return err
}
if dstDialect.URI().DBType == schemas.MYSQL {
// For MySQL set NO_BACKLASH_ESCAPES so that strings work properly
if _, err := io.WriteString(w, "SET sql_mode='NO_BACKSLASH_ESCAPES';\n"); err != nil {
return err
}
}
for i, table := range tables {
dstTable := table
if table.Type != nil {
@ -494,8 +514,11 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch
}
dstTableName := dstTable.Name
quoter := dstDialect.Quoter().Quote
quotedDstTableName := quoter(dstTable.Name)
if dstDialect.URI().Schema != "" {
dstTableName = fmt.Sprintf("%s.%s", dstDialect.URI().Schema, dstTable.Name)
quotedDstTableName = fmt.Sprintf("%s.%s", quoter(dstDialect.URI().Schema), quoter(dstTable.Name))
}
originalTableName := table.Name
if engine.dialect.URI().Schema != "" {
@ -508,13 +531,26 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch
}
}
sqls, _ := dstDialect.CreateTableSQL(dstTable, dstTableName)
for _, s := range sqls {
_, err = io.WriteString(w, s+";\n")
if dstTable.AutoIncrement != "" && dstDialect.Features().AutoincrMode == dialects.SequenceAutoincrMode {
sqlstr, err := dstDialect.CreateSequenceSQL(ctx, engine.db, utils.SeqName(dstTableName))
if err != nil {
return err
}
_, err = io.WriteString(w, sqlstr+";\n")
if err != nil {
return err
}
}
sqlstr, _, err := dstDialect.CreateTableSQL(ctx, engine.db, dstTable, dstTableName)
if err != nil {
return err
}
_, err = io.WriteString(w, sqlstr+";\n")
if err != nil {
return err
}
if len(dstTable.PKColumns()) > 0 && dstDialect.URI().DBType == schemas.MSSQL {
fmt.Fprintf(w, "SET IDENTITY_INSERT [%s] ON;\n", dstTable.Name)
}
@ -551,7 +587,7 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch
sess := engine.NewSession()
defer sess.Close()
for rows.Next() {
_, err = io.WriteString(w, "INSERT INTO "+dstDialect.Quoter().Quote(dstTableName)+" ("+destColNames+") VALUES (")
_, err = io.WriteString(w, "INSERT INTO "+quotedDstTableName+" ("+destColNames+") VALUES (")
if err != nil {
return err
}
@ -562,36 +598,224 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch
}
for i, scanResult := range scanResults {
stp := schemas.SQLType{Name: types[i].DatabaseTypeName()}
if stp.IsNumeric() {
s := scanResult.(*sql.NullString)
if s.Valid {
if _, err = io.WriteString(w, formatBool(s.String, dstDialect)); err != nil {
return err
}
} else {
if _, err = io.WriteString(w, "NULL"); err != nil {
return err
}
}
} else if stp.IsBool() {
s := scanResult.(*sql.NullString)
if s.Valid {
if _, err = io.WriteString(w, formatBool(s.String, dstDialect)); err != nil {
return err
}
} else {
if _, err = io.WriteString(w, "NULL"); err != nil {
return err
}
s := scanResult.(*sql.NullString)
if !s.Valid {
if _, err = io.WriteString(w, "NULL"); err != nil {
return err
}
} else {
s := scanResult.(*sql.NullString)
if s.Valid {
if _, err = io.WriteString(w, "'"+strings.ReplaceAll(s.String, "'", "''")+"'"); err != nil {
if table.Columns()[i].SQLType.IsBool() || stp.IsBool() || (dstDialect.URI().DBType == schemas.MSSQL && strings.EqualFold(stp.Name, schemas.Bit)) {
val, err := strconv.ParseBool(s.String)
if err != nil {
return err
}
if _, err = io.WriteString(w, formatBool(val, dstDialect)); err != nil {
return err
}
} else if stp.IsNumeric() {
if _, err = io.WriteString(w, s.String); err != nil {
return err
}
} else if sess.engine.dialect.URI().DBType == schemas.DAMENG && stp.IsTime() && len(s.String) == 25 {
r := strings.ReplaceAll(s.String[:19], "T", " ")
if _, err = io.WriteString(w, "'"+r+"'"); err != nil {
return err
}
} else if len(s.String) == 0 {
if _, err := io.WriteString(w, "''"); err != nil {
return err
}
} else if dstDialect.URI().DBType == schemas.POSTGRES {
if dstTable.Columns()[i].SQLType.IsBlob() {
// Postgres has the escape format and we should use that for bytea data
if _, err := fmt.Fprintf(w, "'\\x%x'", s.String); err != nil {
return err
}
} else {
// Postgres concatentates strings using || (NOTE: a NUL byte in a text segment will fail)
toCheck := strings.ReplaceAll(s.String, "'", "''")
for len(toCheck) > 0 {
loc := controlCharactersRe.FindStringIndex(toCheck)
if loc == nil {
if _, err := io.WriteString(w, "'"+toCheck+"'"); err != nil {
return err
}
break
}
if loc[0] > 0 {
if _, err := io.WriteString(w, "'"+toCheck[:loc[0]]+"' || "); err != nil {
return err
}
}
if _, err := io.WriteString(w, "e'"); err != nil {
return err
}
for i := loc[0]; i < loc[1]; i++ {
if _, err := fmt.Fprintf(w, "\\x%02x", toCheck[i]); err != nil {
return err
}
}
toCheck = toCheck[loc[1]:]
if len(toCheck) > 0 {
if _, err := io.WriteString(w, "' || "); err != nil {
return err
}
} else {
if _, err := io.WriteString(w, "'"); err != nil {
return err
}
}
}
}
} else if dstDialect.URI().DBType == schemas.MYSQL {
loc := controlCharactersRe.FindStringIndex(s.String)
if loc == nil {
if _, err := io.WriteString(w, "'"+strings.ReplaceAll(s.String, "'", "''")+"'"); err != nil {
return err
}
} else {
if _, err := io.WriteString(w, "CONCAT("); err != nil {
return err
}
toCheck := strings.ReplaceAll(s.String, "'", "''")
for len(toCheck) > 0 {
loc := controlCharactersRe.FindStringIndex(toCheck)
if loc == nil {
if _, err := io.WriteString(w, "'"+toCheck+"')"); err != nil {
return err
}
break
}
if loc[0] > 0 {
if _, err := io.WriteString(w, "'"+toCheck[:loc[0]]+"', "); err != nil {
return err
}
}
for i := loc[0]; i < loc[1]-1; i++ {
if _, err := io.WriteString(w, "CHAR("+strconv.Itoa(int(toCheck[i]))+"), "); err != nil {
return err
}
}
char := toCheck[loc[1]-1]
toCheck = toCheck[loc[1]:]
if len(toCheck) > 0 {
if _, err := io.WriteString(w, "CHAR("+strconv.Itoa(int(char))+"), "); err != nil {
return err
}
} else {
if _, err = io.WriteString(w, "CHAR("+strconv.Itoa(int(char))+"))"); err != nil {
return err
}
}
}
}
} else if dstDialect.URI().DBType == schemas.SQLITE {
if dstTable.Columns()[i].SQLType.IsBlob() {
// SQLite has its escape format
if _, err := fmt.Fprintf(w, "X'%x'", s.String); err != nil {
return err
}
} else {
// SQLite concatentates strings using || (NOTE: a NUL byte in a text segment will fail)
toCheck := strings.ReplaceAll(s.String, "'", "''")
for len(toCheck) > 0 {
loc := controlCharactersRe.FindStringIndex(toCheck)
if loc == nil {
if _, err := io.WriteString(w, "'"+toCheck+"'"); err != nil {
return err
}
break
}
if loc[0] > 0 {
if _, err := io.WriteString(w, "'"+toCheck[:loc[0]]+"' || "); err != nil {
return err
}
}
if _, err := fmt.Fprintf(w, "X'%x'", toCheck[loc[0]:loc[1]]); err != nil {
return err
}
toCheck = toCheck[loc[1]:]
if len(toCheck) > 0 {
if _, err := io.WriteString(w, " || "); err != nil {
return err
}
}
}
}
} else if dstDialect.URI().DBType == schemas.DAMENG || dstDialect.URI().DBType == schemas.ORACLE {
if dstTable.Columns()[i].SQLType.IsBlob() {
// ORACLE/DAMENG uses HEXTORAW
if _, err := fmt.Fprintf(w, "HEXTORAW('%x')", s.String); err != nil {
return err
}
} else {
// ORACLE/DAMENG concatentates strings in multiple ways but uses CHAR and has CONCAT
// (NOTE: a NUL byte in a text segment will fail)
if _, err := io.WriteString(w, "CONCAT("); err != nil {
return err
}
toCheck := strings.ReplaceAll(s.String, "'", "''")
for len(toCheck) > 0 {
loc := controlCharactersRe.FindStringIndex(toCheck)
if loc == nil {
if _, err := io.WriteString(w, "'"+toCheck+"')"); err != nil {
return err
}
break
}
if loc[0] > 0 {
if _, err := io.WriteString(w, "'"+toCheck[:loc[0]]+"', "); err != nil {
return err
}
}
for i := loc[0]; i < loc[1]-1; i++ {
if _, err := io.WriteString(w, "CHAR("+strconv.Itoa(int(toCheck[i]))+"), "); err != nil {
return err
}
}
char := toCheck[loc[1]-1]
toCheck = toCheck[loc[1]:]
if len(toCheck) > 0 {
if _, err := io.WriteString(w, "CHAR("+strconv.Itoa(int(char))+"), "); err != nil {
return err
}
} else {
if _, err = io.WriteString(w, "CHAR("+strconv.Itoa(int(char))+"))"); err != nil {
return err
}
}
}
}
} else if dstDialect.URI().DBType == schemas.MSSQL {
if dstTable.Columns()[i].SQLType.IsBlob() {
// MSSQL uses CONVERT(VARBINARY(MAX), '0xDEADBEEF', 1)
if _, err := fmt.Fprintf(w, "CONVERT(VARBINARY(MAX), '0x%x', 1)", s.String); err != nil {
return err
}
} else {
if _, err = io.WriteString(w, "N'"+strings.ReplaceAll(s.String, "'", "''")+"'"); err != nil {
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, "NULL"); err != nil {
if _, err = io.WriteString(w, "'"+strings.ReplaceAll(s.String, "'", "''")+"'"); err != nil {
return err
}
}
@ -618,6 +842,9 @@ func (engine *Engine) dumpTables(tables []*schemas.Table, w io.Writer, tp ...sch
return err
}
}
// !datbeohbbh! if no error, manually close
rows.Close()
sess.Close()
}
return nil
}
@ -799,9 +1026,8 @@ func (engine *Engine) Desc(colNames ...string) *Session {
// Asc will generate "ORDER BY column1,column2 Asc"
// This method can chainable use.
//
// engine.Desc("name").Asc("age").Find(&users)
// // SELECT * FROM user ORDER BY name DESC, age ASC
//
// engine.Desc("name").Asc("age").Find(&users)
// // SELECT * FROM user ORDER BY name DESC, age ASC
func (engine *Engine) Asc(colNames ...string) *Session {
session := engine.NewSession()
session.isAutoClose = true
@ -809,10 +1035,10 @@ func (engine *Engine) Asc(colNames ...string) *Session {
}
// OrderBy will generate "ORDER BY order"
func (engine *Engine) OrderBy(order string) *Session {
func (engine *Engine) OrderBy(order interface{}, args ...interface{}) *Session {
session := engine.NewSession()
session.isAutoClose = true
return session.OrderBy(order)
return session.OrderBy(order, args...)
}
// Prepare enables prepare statement
@ -823,7 +1049,7 @@ func (engine *Engine) Prepare() *Session {
}
// Join the join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN
func (engine *Engine) Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session {
func (engine *Engine) Join(joinOperator string, tablename interface{}, condition interface{}, args ...interface{}) *Session {
session := engine.NewSession()
session.isAutoClose = true
return session.Join(joinOperator, tablename, condition, args...)
@ -916,112 +1142,6 @@ func (engine *Engine) UnMapType(t reflect.Type) {
engine.tagParser.ClearCacheTable(t)
}
// Sync the new struct changes to database, this method will automatically add
// table, column, index, unique. but will not delete or change anything.
// If you change some field, you should change the database manually.
func (engine *Engine) Sync(beans ...interface{}) error {
session := engine.NewSession()
defer session.Close()
for _, bean := range beans {
v := utils.ReflectValue(bean)
tableNameNoSchema := dialects.FullTableName(engine.dialect, engine.GetTableMapper(), bean)
table, err := engine.tagParser.ParseWithCache(v)
if err != nil {
return err
}
isExist, err := session.Table(bean).isTableExist(tableNameNoSchema)
if err != nil {
return err
}
if !isExist {
err = session.createTable(bean)
if err != nil {
return err
}
}
/*isEmpty, err := engine.IsEmptyTable(bean)
if err != nil {
return err
}*/
var isEmpty bool
if isEmpty {
err = session.dropTable(bean)
if err != nil {
return err
}
err = session.createTable(bean)
if err != nil {
return err
}
} else {
for _, col := range table.Columns() {
isExist, err := engine.dialect.IsColumnExist(engine.db, session.ctx, tableNameNoSchema, col.Name)
if err != nil {
return err
}
if !isExist {
if err := session.statement.SetRefBean(bean); err != nil {
return err
}
err = session.addColumn(col.Name)
if err != nil {
return err
}
}
}
for name, index := range table.Indexes {
if err := session.statement.SetRefBean(bean); err != nil {
return err
}
if index.Type == schemas.UniqueType {
isExist, err := session.isIndexExist2(tableNameNoSchema, index.Cols, true)
if err != nil {
return err
}
if !isExist {
if err := session.statement.SetRefBean(bean); err != nil {
return err
}
err = session.addUnique(tableNameNoSchema, name)
if err != nil {
return err
}
}
} else if index.Type == schemas.IndexType {
isExist, err := session.isIndexExist2(tableNameNoSchema, index.Cols, false)
if err != nil {
return err
}
if !isExist {
if err := session.statement.SetRefBean(bean); err != nil {
return err
}
err = session.addIndex(tableNameNoSchema, name)
if err != nil {
return err
}
}
} else {
return errors.New("unknow index type")
}
}
}
}
return nil
}
// Sync2 synchronize structs to database tables
func (engine *Engine) Sync2(beans ...interface{}) error {
s := engine.NewSession()
defer s.Close()
return s.Sync2(beans...)
}
// CreateTables create tabls according bean
func (engine *Engine) CreateTables(beans ...interface{}) error {
session := engine.NewSession()
@ -1035,7 +1155,7 @@ func (engine *Engine) CreateTables(beans ...interface{}) error {
for _, bean := range beans {
err = session.createTable(bean)
if err != nil {
session.Rollback()
_ = session.Rollback()
return err
}
}
@ -1055,7 +1175,7 @@ func (engine *Engine) DropTables(beans ...interface{}) error {
for _, bean := range beans {
err = session.dropTable(bean)
if err != nil {
session.Rollback()
_ = session.Rollback()
return err
}
}
@ -1108,15 +1228,16 @@ func (engine *Engine) Insert(beans ...interface{}) (int64, error) {
func (engine *Engine) InsertOne(bean interface{}) (int64, error) {
session := engine.NewSession()
defer session.Close()
return session.InsertOne(bean)
return session.Insert(bean)
}
// Update records, bean's non-empty fields are updated contents,
// condiBean' non-empty filds are conditions
// CAUTION:
// 1.bool will defaultly be updated content nor conditions
// You should call UseBool if you have bool to use.
// 2.float32 & float64 may be not inexact as conditions
//
// 1.bool will defaultly be updated content nor conditions
// You should call UseBool if you have bool to use.
// 2.float32 & float64 may be not inexact as conditions
func (engine *Engine) Update(bean interface{}, condiBeans ...interface{}) (int64, error) {
session := engine.NewSession()
defer session.Close()
@ -1124,18 +1245,27 @@ func (engine *Engine) Update(bean interface{}, condiBeans ...interface{}) (int64
}
// Delete records, bean's non-empty fields are conditions
// At least one condition must be set.
func (engine *Engine) Delete(beans ...interface{}) (int64, error) {
session := engine.NewSession()
defer session.Close()
return session.Delete(beans...)
}
// Get retrieve one record from table, bean's non-empty fields
// are conditions
func (engine *Engine) Get(bean interface{}) (bool, error) {
// Truncate records, bean's non-empty fields are conditions
// In contrast to Delete this method allows deletes without conditions.
func (engine *Engine) Truncate(beans ...interface{}) (int64, error) {
session := engine.NewSession()
defer session.Close()
return session.Get(bean)
return session.Truncate(beans...)
}
// Get retrieve one record from table, bean's non-empty fields
// are conditions
func (engine *Engine) Get(beans ...interface{}) (bool, error) {
session := engine.NewSession()
defer session.Close()
return session.Get(beans...)
}
// Exist returns true if the record exist otherwise return false
@ -1226,13 +1356,13 @@ func (engine *Engine) Import(r io.Reader) ([]sql.Result, error) {
}
// nowTime return current time
func (engine *Engine) nowTime(col *schemas.Column) (interface{}, time.Time) {
func (engine *Engine) nowTime(col *schemas.Column) (interface{}, time.Time, error) {
t := time.Now()
var tz = engine.DatabaseTZ
if !col.DisableTimeZone && col.TimeZone != nil {
tz = col.TimeZone
result, err := dialects.FormatColumnTime(engine.dialect, engine.DatabaseTZ, col, t)
if err != nil {
return nil, time.Time{}, err
}
return dialects.FormatTime(engine.dialect, col.SQLType.Name, t.In(tz)), t.In(engine.TZLocation)
return result, t.In(engine.TZLocation), nil
}
// GetColumnMapper returns the column name mapper
@ -1325,3 +1455,10 @@ func (engine *Engine) Transaction(f func(*Session) (interface{}, error)) (interf
return result, nil
}
func (engine *Engine) IndexHint(op, forType, indexerOrColName string) *Session {
session := engine.NewSession()
session.isAutoClose = true
session.statement.LastError = session.statement.IndexHint(op, forType, indexerOrColName)
return session
}

62
go.mod
View File

@ -1,19 +1,57 @@
module xorm.io/xorm
go 1.13
go 1.18
require (
github.com/denisenkom/go-mssqldb v0.10.0
github.com/go-sql-driver/mysql v1.6.0
github.com/goccy/go-json v0.7.4
github.com/json-iterator/go v1.1.11
github.com/lib/pq v1.10.2
github.com/mattn/go-sqlite3 v1.14.8
github.com/shopspring/decimal v1.2.0
github.com/stretchr/testify v1.7.0
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.2
github.com/json-iterator/go v1.1.12
github.com/lib/pq v1.10.7
github.com/mattn/go-sqlite3 v1.14.16
github.com/microsoft/go-mssqldb v1.6.0
github.com/shopspring/decimal v1.3.1
github.com/stretchr/testify v1.8.4
github.com/syndtr/goleveldb v1.0.0
github.com/ziutek/mymysql v1.5.4
gopkg.in/yaml.v2 v2.2.2 // indirect
modernc.org/sqlite v1.11.2
xorm.io/builder v0.3.9
modernc.org/sqlite v1.20.4
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
github.com/golang-sql/sqlexp v0.1.0 // indirect
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.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.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
github.com/mattn/go-isatty v0.0.16 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
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.20.0 // indirect
golang.org/x/mod v0.8.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
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.22.2 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.4.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
)

325
go.sum
View File

@ -1,165 +1,290 @@
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
gitee.com/travelliu/dm v1.8.11192 h1:aqJT0xhodZjRutIfEXxKYv0CxqmHUHzsbz6SFaRL6OY=
gitee.com/travelliu/dm v1.8.11192/go.mod h1:DHTzyhCrM843x9VdKVbZ+GKXGRbKM2sJ4LxihRxShkE=
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.7.1 h1:/iHxaJhsFr0+xVFfbMr5vxz848jyiWuIEDhYq3y5odY=
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.3.0 h1:vcYCAze6p19qBW7MhZybIsqD8sMV8js0NyQM8JDnVtg=
github.com/Azure/azure-sdk-for-go/sdk/internal v1.3.0 h1:sXr+ck84g/ZlZUOZiNELInmMgOsuGwdjjVkEIde0OtY=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.0 h1:yfJe15aSwEQ6Oo6J+gdfdulPNoZ3TEhmbhLIoxZcA+U=
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v0.8.0 h1:T028gtTPiYt/RMUfs8nVsAL7FDQrfLlrm/NnRG/zcC4=
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.0 h1:HCc0+LpPfpCKs6LGGLAhwBARt9632unrVcI6i8s/8os=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.9.0 h1:RSohk2RsiZqLZ0zCjtfn3S4Gp4exhpBWHyQ7D0yGjAk=
github.com/denisenkom/go-mssqldb v0.9.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/denisenkom/go-mssqldb v0.10.0 h1:QykgLZBorFE95+gO3u9esLd0BmbvpWp0/waNNZfHBM8=
github.com/denisenkom/go-mssqldb v0.10.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/goccy/go-json v0.7.4 h1:B44qRUFwz/vxPKPISQ1KhvzRi9kZ28RAf6YtjriBZ5k=
github.com/goccy/go-json v0.7.4/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/goccy/go-json v0.8.1 h1:4/Wjm0JIJaTDm8K1KcGrLHJoa8EsJ13YWeX+6Kfq6uI=
github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.3 h1:x95R7cp+rSeeqAMI2knLtQ0DKlaBhv2NrtrOvafPHRo=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
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.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=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM=
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.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=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM=
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
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.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/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=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/lib/pq v1.7.0 h1:h93mCPfUSkaul3Ka/VG8uZdmW1uMHDGxzu0NWHuJmHY=
github.com/lib/pq v1.7.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/lib/pq v1.10.7 h1:p7ZhMD+KsSRozJr34udlUrhboJwWAgCg34+/ZZNvZZw=
github.com/lib/pq v1.10.7/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-sqlite3 v1.14.6 h1:dNPt6NO46WmLVt2DLNpwczCmdV5boIZ6g/tlDrlRUbg=
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/microsoft/go-mssqldb v1.6.0 h1:mM3gYdVwEPFrlg/Dvr2DNVEgYFG7L42l+dGc67NNNpc=
github.com/microsoft/go-mssqldb v1.6.0/go.mod h1:00mDtPbeQCRGC1HwOOR5K/gr30P1NcEG0vx6Kbv2aJU=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
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/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 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
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.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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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.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.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=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20201021035429-f5854403a974 h1:IX6qOQeG5uLjB/hjjwjedwfjND0hgjPMMyO1RoIXQNI=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
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.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-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/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=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201126233918-771906719818/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c h1:VwygUrnw9jn88c4u8GD3rZQbqrP/tgas88tPUbBxQrk=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20220811171246-fbc7d0a398ab/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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
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.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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78 h1:M8tBwCtWD/cZV9DZpFYRUgaymAYAr+aIUTWzDaM3uPs=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
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=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
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-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
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=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/uint128 v1.1.1 h1:pnxCASz787iMf+02ssImqk6OLt+Z5QHMoZyUXR4z6JU=
lukechampine.com/uint128 v1.1.1/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009 h1:u0oCo5b9wyLr++HF3AN9JicGhkUxJhMz51+8TIZH9N0=
modernc.org/cc/v3 v3.31.5-0.20210308123301-7a3e9dab9009/go.mod h1:0R6jl1aZlIl2avnYfbfHBS1QB6/f+16mihBObaBC878=
modernc.org/cc/v3 v3.33.6 h1:r63dgSzVzRxUpAJFPQWHy1QeZeY1ydNENUDaBx1GqYc=
modernc.org/cc/v3 v3.33.6/go.mod h1:iPJg1pkwXqAV16SNgFBVYmggfMg6xhs+2oiO0vclK3g=
modernc.org/ccgo/v3 v3.9.0 h1:JbcEIqjw4Agf+0g3Tc85YvfYqkkFOv6xBwS4zkfqSoA=
modernc.org/ccgo/v3 v3.9.0/go.mod h1:nQbgkn8mwzPdp4mm6BT6+p85ugQ7FrGgIcYaE7nSrpY=
modernc.org/ccgo/v3 v3.9.5 h1:dEuUSf8WN51rDkprFuAqjfchKEzN0WttP/Py3enBwjk=
modernc.org/ccgo/v3 v3.9.5/go.mod h1:umuo2EP2oDSBnD3ckjaVUXMrmeAw8C8OSICVa0iFf60=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.7.13-0.20210308123627-12f642a52bb8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/libc v1.8.0 h1:Pp4uv9g0csgBMpGPABKtkieF6O5MGhfGo6ZiOdlYfR8=
modernc.org/libc v1.8.0/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/libc v1.9.8/go.mod h1:U1eq8YWr/Kc1RWCMFUWEdkTg8OTcfLw2kY8EDwl039w=
modernc.org/libc v1.9.11 h1:QUxZMs48Ahg2F7SN41aERvMfGLY2HU/ADnB9DC4Yts8=
modernc.org/libc v1.9.11/go.mod h1:NyF3tsA5ArIjJ83XB0JlqhjTabTCHm9aX4XMPHyQn0Q=
modernc.org/mathutil v1.1.1/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.2.2 h1:+yFk8hBprV+4c0U9GjFtL+dV3N8hOJ8JCituQcMShFY=
modernc.org/mathutil v1.2.2/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/mathutil v1.4.0 h1:GCjoRaBew8ECCKINQA2nYjzvufFW9YiEuuB+rQ9bn2E=
modernc.org/mathutil v1.4.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.0.4 h1:utMBrFcpnQDdNsmM6asmyH/FM9TqLPS7XF7otpJmrwM=
modernc.org/memory v1.0.4/go.mod h1:nV2OApxradM3/OVbs2/0OsP6nPfakXpi50C7dcoHXlc=
modernc.org/opt v0.1.1 h1:/0RX92k9vwVeDXj+Xn23DKp2VJubL7k8qNffND6qn3A=
modernc.org/opt v0.1.1/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84 h1:rgEUzE849tFlHSoeCrKyS9cZAljC+DY7MdMHKq6R6sY=
modernc.org/sqlite v1.10.1-0.20210314190707-798bbeb9bb84/go.mod h1:PGzq6qlhyYjL6uVbSgS6WoF7ZopTW/sI7+7p+mb4ZVU=
modernc.org/sqlite v1.11.2 h1:ShWQpeD3ag/bmx6TqidBlIWonWmQaSQKls3aenCbt+w=
modernc.org/sqlite v1.11.2/go.mod h1:+mhs/P1ONd+6G7hcAs6irwDi/bjTQ7nLW6LHRBsEa3A=
modernc.org/strutil v1.1.0 h1:+1/yCzZxY2pZwwrsbH+4T7BQMoLQ9QiBshRC9eicYsc=
modernc.org/strutil v1.1.0/go.mod h1:lstksw84oURvj9y3tn8lGvRxyRC1S2+g5uuIzNfIOBs=
modernc.org/strutil v1.1.1 h1:xv+J1BXY3Opl2ALrBwyfEikFAj8pmqcpnfmuwUwcozs=
modernc.org/strutil v1.1.1/go.mod h1:DE+MQQ/hjKBZS2zNInV5hhcipt5rLPWkmpbGeW5mmdw=
modernc.org/tcl v1.5.0 h1:euZSUNfE0Fd4W8VqXI1Ly1v7fqDJoBuAV88Ea+SnaSs=
modernc.org/tcl v1.5.0/go.mod h1:gb57hj4pO8fRrK54zveIfFXBaMHK3SKJNWcmRw1cRzc=
modernc.org/tcl v1.5.5/go.mod h1:ADkaTUuwukkrlhqwERyq0SM8OvyXo7+TjFz7yAF56EI=
modernc.org/token v1.0.0 h1:a0jaWiNMDhDUtqOj09wvjWWAqd3q7WpBulmL9H2egsk=
modernc.org/token v1.0.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.0.1-0.20210308123920-1f282aa71362/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
modernc.org/z v1.0.1 h1:WyIDpEpAIx4Hel6q/Pcgj/VhaQV5XPJ2I6ryIYbjnpc=
modernc.org/z v1.0.1/go.mod h1:8/SRk5C/HgiQWCgXdfpb+1RvhORdkz5sw72d3jjtyqA=
xorm.io/builder v0.3.8 h1:P/wPgRqa9kX5uE0aA1/ukJ23u9KH0aSRpHLwDKXigSE=
xorm.io/builder v0.3.8/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
xorm.io/builder v0.3.9 h1:Sd65/LdWyO7LR8+Cbd+e7mm3sK/7U9k0jS3999IDHMc=
xorm.io/builder v0.3.9/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=
modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0=
modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk=
modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE=
modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/tcl v1.15.0 h1:oY+JeD11qVVSgVvodMJsu7Edf8tr5E/7tuhF5cNYz34=
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978 h1:bvLlAPW1ZMTWA32LuZMBEGHAUOcATZjzHcotf3SWweM=
xorm.io/builder v0.3.11-0.20220531020008-1bd24a7dc978/go.mod h1:aUW0S9eb9VCaPohFCH3j7czOx1PMW3i1HrSzbLYGBSE=

View File

@ -1,164 +0,0 @@
// Copyright 2017 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 integrations
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestRows(t *testing.T) {
assert.NoError(t, PrepareEngine())
type UserRows struct {
Id int64
IsMan bool
}
assert.NoError(t, testEngine.Sync2(new(UserRows)))
cnt, err := testEngine.Insert(&UserRows{
IsMan: true,
})
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
rows, err := testEngine.Rows(new(UserRows))
assert.NoError(t, err)
defer rows.Close()
cnt = 0
user := new(UserRows)
for rows.Next() {
err = rows.Scan(user)
assert.NoError(t, err)
cnt++
}
assert.EqualValues(t, 1, cnt)
assert.False(t, rows.Next())
assert.NoError(t, rows.Close())
rows0, err := testEngine.Where("1>1").Rows(new(UserRows))
assert.NoError(t, err)
defer rows0.Close()
cnt = 0
user0 := new(UserRows)
for rows0.Next() {
err = rows0.Scan(user0)
assert.NoError(t, err)
cnt++
}
assert.EqualValues(t, 0, cnt)
assert.NoError(t, rows0.Close())
sess := testEngine.NewSession()
defer sess.Close()
rows1, err := sess.Prepare().Rows(new(UserRows))
assert.NoError(t, err)
defer rows1.Close()
cnt = 0
for rows1.Next() {
err = rows1.Scan(user)
assert.NoError(t, err)
cnt++
}
assert.EqualValues(t, 1, cnt)
var tbName = testEngine.Quote(testEngine.TableName(user, true))
rows2, err := testEngine.SQL("SELECT * FROM " + tbName).Rows(new(UserRows))
assert.NoError(t, err)
defer rows2.Close()
cnt = 0
for rows2.Next() {
err = rows2.Scan(user)
assert.NoError(t, err)
cnt++
}
assert.EqualValues(t, 1, cnt)
}
func TestRowsMyTableName(t *testing.T) {
assert.NoError(t, PrepareEngine())
type UserRowsMyTable struct {
Id int64
IsMan bool
}
var tableName = "user_rows_my_table_name"
assert.NoError(t, testEngine.Table(tableName).Sync2(new(UserRowsMyTable)))
cnt, err := testEngine.Table(tableName).Insert(&UserRowsMyTable{
IsMan: true,
})
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
rows, err := testEngine.Table(tableName).Rows(new(UserRowsMyTable))
assert.NoError(t, err)
cnt = 0
user := new(UserRowsMyTable)
for rows.Next() {
err = rows.Scan(user)
assert.NoError(t, err)
cnt++
}
assert.EqualValues(t, 1, cnt)
rows.Close()
rows, err = testEngine.Table(tableName).Rows(&UserRowsMyTable{
Id: 2,
})
assert.NoError(t, err)
cnt = 0
user = new(UserRowsMyTable)
for rows.Next() {
err = rows.Scan(user)
assert.NoError(t, err)
cnt++
}
assert.EqualValues(t, 0, cnt)
}
type UserRowsSpecTable struct {
Id int64
IsMan bool
}
func (UserRowsSpecTable) TableName() string {
return "user_rows_my_table_name"
}
func TestRowsSpecTableName(t *testing.T) {
assert.NoError(t, PrepareEngine())
assert.NoError(t, testEngine.Sync2(new(UserRowsSpecTable)))
cnt, err := testEngine.Insert(&UserRowsSpecTable{
IsMan: true,
})
assert.NoError(t, err)
assert.EqualValues(t, 1, cnt)
rows, err := testEngine.Rows(new(UserRowsSpecTable))
assert.NoError(t, err)
defer rows.Close()
cnt = 0
user := new(UserRowsSpecTable)
for rows.Next() {
err = rows.Scan(user)
assert.NoError(t, err)
cnt++
}
assert.EqualValues(t, 1, cnt)
}

View File

@ -31,13 +31,14 @@ type Interface interface {
Decr(column string, arg ...interface{}) *Session
Desc(...string) *Session
Delete(...interface{}) (int64, error)
Truncate(...interface{}) (int64, error)
Distinct(columns ...string) *Session
DropIndexes(bean interface{}) error
Exec(sqlOrArgs ...interface{}) (sql.Result, error)
Exist(bean ...interface{}) (bool, error)
Find(interface{}, ...interface{}) error
FindAndCount(interface{}, ...interface{}) (int64, error)
Get(interface{}) (bool, error)
Get(...interface{}) (bool, error)
GroupBy(keys string) *Session
ID(interface{}) *Session
In(string, ...interface{}) *Session
@ -52,9 +53,9 @@ type Interface interface {
NoAutoCondition(...bool) *Session
NotIn(string, ...interface{}) *Session
Nullable(...string) *Session
Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session
Join(joinOperator string, tablename interface{}, condition interface{}, args ...interface{}) *Session
Omit(columns ...string) *Session
OrderBy(order string) *Session
OrderBy(order interface{}, args ...interface{}) *Session
Ping() error
Query(sqlOrArgs ...interface{}) (resultsSlice []map[string][]byte, err error)
QueryInterface(sqlOrArgs ...interface{}) ([]map[string]interface{}, error)
@ -99,6 +100,7 @@ type EngineInterface interface {
MapCacher(interface{}, caches.Cacher) error
NewSession() *Session
NoAutoTime() *Session
Prepare() *Session
Quote(string) string
SetCacher(string, caches.Cacher)
SetConnMaxLifetime(time.Duration)
@ -119,6 +121,7 @@ type EngineInterface interface {
ShowSQL(show ...bool)
Sync(...interface{}) error
Sync2(...interface{}) error
SyncWithOptions(SyncOptions, ...interface{}) (*SyncResult, error)
StoreEngine(storeEngine string) *Session
TableInfo(bean interface{}) (*schemas.Table, error)
TableName(interface{}, ...bool) string

View File

@ -1,30 +0,0 @@
// Copyright 2021 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 convert
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestString2Time(t *testing.T) {
expectedLoc, err := time.LoadLocation("Asia/Shanghai")
assert.NoError(t, err)
var kases = map[string]time.Time{
"2021-06-06T22:58:20+08:00": time.Date(2021, 6, 6, 22, 58, 20, 0, expectedLoc),
"2021-07-11 10:44:00": time.Date(2021, 7, 11, 18, 44, 0, 0, expectedLoc),
"2021-08-10T10:33:04Z": time.Date(2021, 8, 10, 18, 33, 04, 0, expectedLoc),
}
for layout, tm := range kases {
t.Run(layout, func(t *testing.T) {
target, err := String2Time(layout, time.UTC, expectedLoc)
assert.NoError(t, err)
assert.EqualValues(t, tm, *target)
})
}
}

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build gojson
// +build gojson
package json

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build jsoniter
// +build jsoniter
package json

View File

@ -0,0 +1,80 @@
// Copyright 2019 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 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) {
case *builder.Builder:
if _, err := w.WriteString("("); err != nil {
return err
}
if err := argv.WriteTo(w); err != nil {
return err
}
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
}
if v, ok := arg.(bool); ok && statement.dialect.URI().DBType == schemas.MSSQL {
if v {
w.Append(1)
} else {
w.Append(0)
}
} else {
w.Append(arg)
}
}
return nil
}
// WriteArgs writes args
func (statement *Statement) WriteArgs(w *builder.BytesWriter, args []interface{}) error {
for i, arg := range args {
if err := statement.WriteArg(w, arg); err != nil {
return err
}
if i+1 != len(args) {
if _, err := w.WriteString(","); err != nil {
return err
}
}
}
return nil
}

View File

@ -6,6 +6,7 @@ package statements
import (
"fmt"
"strconv"
"strings"
"xorm.io/xorm/internal/utils"
@ -26,14 +27,19 @@ func (statement *Statement) ConvertIDSQL(sqlStr string) string {
return ""
}
var top string
var b strings.Builder
b.WriteString("SELECT ")
pLimitN := statement.LimitN
if pLimitN != nil && statement.dialect.URI().DBType == schemas.MSSQL {
top = fmt.Sprintf("TOP %d ", *pLimitN)
b.WriteString("TOP ")
b.WriteString(strconv.Itoa(*pLimitN))
b.WriteString(" ")
}
b.WriteString(colstrs)
b.WriteString(" FROM ")
b.WriteString(sqls[1])
newsql := fmt.Sprintf("SELECT %s%s FROM %v", top, colstrs, sqls[1])
return newsql
return b.String()
}
return ""
}
@ -54,7 +60,7 @@ func (statement *Statement) ConvertUpdateSQL(sqlStr string) (string, string) {
return "", ""
}
var whereStr = sqls[1]
whereStr := sqls[1]
// TODO: for postgres only, if any other database?
var paraStr string

111
internal/statements/cond.go Normal file
View File

@ -0,0 +1,111 @@
// Copyright 2022 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 statements
import (
"xorm.io/builder"
"xorm.io/xorm/schemas"
)
type QuoteReplacer struct {
*builder.BytesWriter
quoter schemas.Quoter
}
func (q *QuoteReplacer) Write(p []byte) (n int, err error) {
c := q.quoter.Replace(string(p))
return q.BytesWriter.Builder.WriteString(c)
}
func (statement *Statement) QuoteReplacer(w *builder.BytesWriter) *QuoteReplacer {
return &QuoteReplacer{
BytesWriter: w,
quoter: statement.dialect.Quoter(),
}
}
// Where add Where statement
func (statement *Statement) Where(query interface{}, args ...interface{}) *Statement {
return statement.And(query, args...)
}
// And add Where & and statement
func (statement *Statement) And(query interface{}, args ...interface{}) *Statement {
switch qr := query.(type) {
case string:
cond := builder.Expr(qr, args...)
statement.cond = statement.cond.And(cond)
case map[string]interface{}:
cond := make(builder.Eq)
for k, v := range qr {
cond[statement.quote(k)] = v
}
statement.cond = statement.cond.And(cond)
case builder.Cond:
statement.cond = statement.cond.And(qr)
for _, v := range args {
if vv, ok := v.(builder.Cond); ok {
statement.cond = statement.cond.And(vv)
}
}
default:
statement.LastError = ErrConditionType
}
return statement
}
// Or add Where & Or statement
func (statement *Statement) Or(query interface{}, args ...interface{}) *Statement {
switch qr := query.(type) {
case string:
cond := builder.Expr(qr, args...)
statement.cond = statement.cond.Or(cond)
case map[string]interface{}:
cond := make(builder.Eq)
for k, v := range qr {
cond[statement.quote(k)] = v
}
statement.cond = statement.cond.Or(cond)
case builder.Cond:
statement.cond = statement.cond.Or(qr)
for _, v := range args {
if vv, ok := v.(builder.Cond); ok {
statement.cond = statement.cond.Or(vv)
}
}
default:
statement.LastError = ErrConditionType
}
return statement
}
// In generate "Where column IN (?) " statement
func (statement *Statement) In(column string, args ...interface{}) *Statement {
in := builder.In(statement.quote(column), args...)
statement.cond = statement.cond.And(in)
return statement
}
// NotIn generate "Where column NOT IN (?) " statement
func (statement *Statement) NotIn(column string, args ...interface{}) *Statement {
notIn := builder.NotIn(statement.quote(column), args...)
statement.cond = statement.cond.And(notIn)
return statement
}
// SetNoAutoCondition if you do not want convert bean's field as query condition, then use this function
func (statement *Statement) SetNoAutoCondition(no ...bool) *Statement {
statement.NoAutoCondition = true
if len(no) > 0 {
statement.NoAutoCondition = no[0]
}
return statement
}
// Conds returns condtions
func (statement *Statement) Conds() builder.Cond {
return statement.cond
}

View File

@ -0,0 +1,125 @@
// Copyright 2023 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 statements
import (
"errors"
"fmt"
"time"
"xorm.io/builder"
"xorm.io/xorm/internal/utils"
"xorm.io/xorm/schemas"
)
func (statement *Statement) writeDeleteOrder(w *builder.BytesWriter) error {
if err := statement.writeOrderBys(w); err != nil {
return err
}
if statement.LimitN != nil && *statement.LimitN > 0 {
if statement.Start > 0 {
return fmt.Errorf("Delete with Limit start is unsupported")
}
limitNValue := *statement.LimitN
if _, err := fmt.Fprintf(w, " LIMIT %d", limitNValue); err != nil {
return err
}
}
return nil
}
// ErrNotImplemented not implemented
var ErrNotImplemented = errors.New("Not implemented")
func (statement *Statement) writeOrderCond(orderCondWriter *builder.BytesWriter, tableName string) error {
orderSQLWriter := builder.NewWriter()
if err := statement.writeDeleteOrder(orderSQLWriter); err != nil {
return err
}
if orderSQLWriter.Len() == 0 {
return nil
}
switch statement.dialect.URI().DBType {
case schemas.POSTGRES:
if statement.cond.IsValid() {
if _, err := fmt.Fprint(orderCondWriter, " AND "); err != nil {
return err
}
} else {
if _, err := fmt.Fprint(orderCondWriter, " WHERE "); err != nil {
return err
}
}
if _, err := fmt.Fprintf(orderCondWriter, "ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQLWriter.String()); err != nil {
return err
}
orderCondWriter.Append(orderSQLWriter.Args()...)
return nil
case schemas.SQLITE:
if statement.cond.IsValid() {
if _, err := fmt.Fprint(orderCondWriter, " AND "); err != nil {
return err
}
} else {
if _, err := fmt.Fprint(orderCondWriter, " WHERE "); err != nil {
return err
}
}
if _, err := fmt.Fprintf(orderCondWriter, "rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQLWriter.String()); err != nil {
return err
}
orderCondWriter.Append(orderSQLWriter.Args()...)
return nil
// TODO: how to handle delete limit on mssql?
case schemas.MSSQL:
return ErrNotImplemented
default:
return utils.WriteBuilder(orderCondWriter, orderSQLWriter)
}
}
func (statement *Statement) WriteDelete(realSQLWriter, deleteSQLWriter *builder.BytesWriter, nowTime func(*schemas.Column) (interface{}, time.Time, error)) error {
tableNameNoQuote := statement.TableName()
tableName := statement.dialect.Quoter().Quote(tableNameNoQuote)
table := statement.RefTable
if _, err := fmt.Fprint(deleteSQLWriter, "DELETE FROM ", tableName); err != nil {
return err
}
if err := statement.writeWhere(deleteSQLWriter); err != nil {
return err
}
orderCondWriter := builder.NewWriter()
if err := statement.writeOrderCond(orderCondWriter, tableName); err != nil {
return err
}
if statement.GetUnscoped() || table == nil || table.DeletedColumn() == nil { // tag "deleted" is disabled
return utils.WriteBuilder(realSQLWriter, deleteSQLWriter, orderCondWriter)
}
deletedColumn := table.DeletedColumn()
if _, err := fmt.Fprintf(realSQLWriter, "UPDATE %v SET %v = ?",
statement.dialect.Quoter().Quote(statement.TableName()),
statement.dialect.Quoter().Quote(deletedColumn.Name)); err != nil {
return err
}
val, _, err := nowTime(deletedColumn)
if err != nil {
return err
}
realSQLWriter.Append(val)
if err := statement.writeWhere(realSQLWriter); err != nil {
return err
}
return utils.WriteBuilder(realSQLWriter, orderCondWriter)
}

View File

@ -0,0 +1,64 @@
// Copyright 2023 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 statements
import (
"strings"
"xorm.io/builder"
"xorm.io/xorm/schemas"
)
type ErrInvalidIndexHintOperator struct {
Op string
}
func (e ErrInvalidIndexHintOperator) Error() string {
return "invalid index hint operator: " + e.Op
}
func (statement *Statement) IndexHint(op, forType, indexName string) error {
op = strings.ToUpper(op)
statement.indexHints = append(statement.indexHints, indexHint{
op: op,
forType: forType,
indexName: indexName,
})
return nil
}
func (statement *Statement) writeIndexHints(w *builder.BytesWriter) error {
if len(statement.indexHints) == 0 {
return nil
}
switch statement.dialect.URI().DBType {
case schemas.MYSQL:
return statement.writeIndexHintsMySQL(w)
default:
return ErrNotImplemented
}
}
func (statement *Statement) writeIndexHintsMySQL(w *builder.BytesWriter) error {
for _, hint := range statement.indexHints {
if hint.op != "USE" && hint.op != "FORCE" && hint.op != "IGNORE" {
return ErrInvalidIndexHintOperator{Op: hint.op}
}
if err := statement.writeStrings(" ", hint.op, " INDEX")(w); err != nil {
return err
}
if hint.forType != "" {
if err := statement.writeStrings(" FOR ", hint.forType)(w); err != nil {
return err
}
}
if err := statement.writeStrings("(", hint.indexName, ")")(w); err != nil {
return err
}
}
return nil
}

View File

@ -10,6 +10,7 @@ import (
"strings"
"xorm.io/builder"
"xorm.io/xorm/internal/utils"
"xorm.io/xorm/schemas"
)
@ -42,7 +43,19 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{})
return "", nil, err
}
if len(colNames) <= 0 {
hasInsertColumns := len(colNames) > 0
needSeq := len(table.AutoIncrement) > 0 && (statement.dialect.URI().DBType == schemas.ORACLE || statement.dialect.URI().DBType == schemas.DAMENG)
if needSeq {
for _, col := range colNames {
if strings.EqualFold(col, table.AutoIncrement) {
needSeq = false
break
}
}
}
if !hasInsertColumns && statement.dialect.URI().DBType != schemas.ORACLE &&
statement.dialect.URI().DBType != schemas.DAMENG {
if statement.dialect.URI().DBType == schemas.MYSQL {
if _, err := buf.WriteString(" VALUES ()"); err != nil {
return "", nil, err
@ -60,6 +73,10 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{})
return "", nil, err
}
if needSeq {
colNames = append(colNames, table.AutoIncrement)
}
if err := statement.dialect.Quoter().JoinWrite(buf.Builder, append(colNames, exprs.ColNames()...), ","); err != nil {
return "", nil, err
}
@ -72,7 +89,7 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{})
}
if statement.Conds().IsValid() {
if _, err := buf.WriteString(" SELECT "); err != nil {
if err := statement.writeStrings(" SELECT ")(buf); err != nil {
return "", nil, err
}
@ -80,13 +97,23 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{})
return "", nil, err
}
if needSeq {
if len(args) > 0 {
if _, err := buf.WriteString(","); err != nil {
return "", nil, err
}
}
if _, err := buf.WriteString(utils.SeqName(tableName) + ".nextval"); err != nil {
return "", nil, err
}
}
if len(exprs) > 0 {
if _, err := buf.WriteString(","); err != nil {
return "", nil, err
}
}
if err := exprs.WriteArgs(buf); err != nil {
return "", nil, err
if err := exprs.WriteArgs(buf); err != nil {
return "", nil, err
}
}
if _, err := buf.WriteString(" FROM "); err != nil {
@ -97,11 +124,7 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{})
return "", nil, err
}
if _, err := buf.WriteString(" WHERE "); err != nil {
return "", nil, err
}
if err := statement.Conds().WriteTo(buf); err != nil {
if err := statement.writeWhere(buf); err != nil {
return "", nil, err
}
} else {
@ -113,6 +136,18 @@ func (statement *Statement) GenInsertSQL(colNames []string, args []interface{})
return "", nil, err
}
// Insert tablename (id) Values(seq_tablename.nextval)
if needSeq {
if hasInsertColumns {
if _, err := buf.WriteString(","); err != nil {
return "", nil, err
}
}
if _, err := buf.WriteString(utils.SeqName(tableName) + ".nextval"); err != nil {
return "", nil, err
}
}
if len(exprs) > 0 {
if _, err := buf.WriteString(","); err != nil {
return "", nil, err
@ -258,3 +293,99 @@ func (statement *Statement) GenInsertMultipleMapSQL(columns []string, argss [][]
return buf.String(), buf.Args(), nil
}
func (statement *Statement) writeColumns(w *builder.BytesWriter, slice []string) error {
for i, s := range slice {
if i > 0 {
if _, err := fmt.Fprint(w, ","); err != nil {
return err
}
}
if err := statement.dialect.Quoter().QuoteTo(w.Builder, s); err != nil {
return err
}
}
return nil
}
func (statement *Statement) writeQuestions(w *builder.BytesWriter, length int) error {
for i := 0; i < length; i++ {
if i > 0 {
if _, err := fmt.Fprint(w, ","); err != nil {
return err
}
}
if _, err := fmt.Fprint(w, "?"); err != nil {
return err
}
}
return nil
}
func (statement *Statement) oracleWriteInsertMultiple(w *builder.BytesWriter, tableName string, colNames []string, colMultiPlaces []string) error {
if _, err := fmt.Fprint(w, "INSERT ALL"); err != nil {
return err
}
for _, cols := range colMultiPlaces {
if _, err := fmt.Fprint(w, " INTO "); err != nil {
return err
}
if err := statement.dialect.Quoter().QuoteTo(w.Builder, tableName); err != nil {
return err
}
if _, err := fmt.Fprint(w, " ("); err != nil {
return err
}
if err := statement.writeColumns(w, colNames); err != nil {
return err
}
if _, err := fmt.Fprint(w, ") VALUES ("); err != nil {
return err
}
if _, err := fmt.Fprintf(w, cols, ")"); err != nil {
return err
}
}
if _, err := fmt.Fprint(w, " SELECT 1 FROM DUAL"); err != nil {
return err
}
return nil
}
func (statement *Statement) WriteInsertMultiple(w *builder.BytesWriter, tableName string, colNames []string, colMultiPlaces []string) error {
if statement.dialect.URI().DBType == schemas.ORACLE {
return statement.oracleWriteInsertMultiple(w, tableName, colNames, colMultiPlaces)
}
return statement.plainWriteInsertMultiple(w, tableName, colNames, colMultiPlaces)
}
func (statement *Statement) plainWriteInsertMultiple(w *builder.BytesWriter, tableName string, colNames []string, colMultiPlaces []string) error {
if _, err := fmt.Fprint(w, "INSERT INTO "); err != nil {
return err
}
if err := statement.dialect.Quoter().QuoteTo(w.Builder, tableName); err != nil {
return err
}
if _, err := fmt.Fprint(w, " ("); err != nil {
return err
}
if err := statement.writeColumns(w, colNames); err != nil {
return err
}
if _, err := fmt.Fprint(w, ") VALUES ("); err != nil {
return err
}
for i, cols := range colMultiPlaces {
if _, err := fmt.Fprint(w, cols, ")"); err != nil {
return err
}
if i < len(colMultiPlaces)-1 {
if _, err := fmt.Fprint(w, ",("); err != nil {
return err
}
}
}
return nil
}

129
internal/statements/join.go Normal file
View File

@ -0,0 +1,129 @@
// Copyright 2022 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 statements
import (
"fmt"
"strings"
"xorm.io/builder"
"xorm.io/xorm/dialects"
"xorm.io/xorm/internal/utils"
"xorm.io/xorm/schemas"
)
// Join The joinOP should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN
func (statement *Statement) Join(joinOP string, joinTable interface{}, condition interface{}, args ...interface{}) *Statement {
statement.joins = append(statement.joins, join{
op: joinOP,
table: joinTable,
condition: condition,
args: args,
})
return statement
}
func (statement *Statement) writeJoins(w *builder.BytesWriter) error {
for _, join := range statement.joins {
if err := statement.writeJoin(w, join); err != nil {
return err
}
}
return nil
}
func (statement *Statement) writeJoinTable(buf *builder.BytesWriter, join join) error {
switch tp := join.table.(type) {
case builder.Builder:
if _, err := fmt.Fprintf(buf, " ("); err != nil {
return err
}
if err := tp.WriteTo(statement.QuoteReplacer(buf)); err != nil {
return err
}
fields := strings.Split(tp.TableName(), ".")
aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1])
aliasName = schemas.CommonQuoter.Trim(aliasName)
if _, err := fmt.Fprintf(buf, ") %s", statement.quote(aliasName)); err != nil {
return err
}
case *builder.Builder:
if _, err := fmt.Fprintf(buf, " ("); err != nil {
return err
}
if err := tp.WriteTo(statement.QuoteReplacer(buf)); err != nil {
return err
}
fields := strings.Split(tp.TableName(), ".")
aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1])
aliasName = schemas.CommonQuoter.Trim(aliasName)
if _, err := fmt.Fprintf(buf, ") %s", statement.quote(aliasName)); err != nil {
return err
}
default:
tbName := dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), join.table, true)
if !utils.IsSubQuery(tbName) {
var sb strings.Builder
if err := statement.dialect.Quoter().QuoteTo(&sb, tbName); err != nil {
return err
}
tbName = sb.String()
} else {
tbName = statement.ReplaceQuote(tbName)
}
if _, err := fmt.Fprint(buf, " ", tbName); err != nil {
return err
}
}
return nil
}
func (statement *Statement) writeJoin(buf *builder.BytesWriter, join join) error {
// write join operator
if _, err := fmt.Fprint(buf, " ", join.op, " JOIN"); err != nil {
return err
}
// write join table or subquery
if err := statement.writeJoinTable(buf, join); err != nil {
return err
}
// write on condition
if _, err := fmt.Fprint(buf, " ON "); err != nil {
return err
}
switch condTp := join.condition.(type) {
case string:
if _, err := fmt.Fprint(buf, statement.ReplaceQuote(condTp)); err != nil {
return err
}
case builder.Cond:
if err := condTp.WriteTo(statement.QuoteReplacer(buf)); err != nil {
return err
}
default:
return fmt.Errorf("unsupported join condition type: %v", condTp)
}
buf.Append(join.args...)
return nil
}
func (statement *Statement) convertJoinCondition(join join) (builder.Cond, error) {
switch condTp := join.condition.(type) {
case string:
return builder.Expr(statement.ReplaceQuote(condTp), join.args...), nil
case builder.Cond:
return condTp, nil
default:
return nil, fmt.Errorf("unsupported join condition type: %v", condTp)
}
}

View File

@ -0,0 +1,45 @@
// Copyright 2022 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 statements
import (
"xorm.io/builder"
)
// isUsingLegacy returns true if xorm uses legacy LIMIT OFFSET.
// It's only available in sqlserver and oracle, if param USE_LEGACY_LIMIT_OFFSET is set to "true"
func (statement *Statement) isUsingLegacyLimitOffset() bool {
u, ok := statement.dialect.(interface{ UseLegacyLimitOffset() bool })
return ok && u.UseLegacyLimitOffset()
}
// write mssql legacy query sql
func (statement *Statement) writeMssqlLegacySelect(buf *builder.BytesWriter, columnStr string) error {
return statement.writeMultiple(buf,
statement.writeStrings("SELECT"),
statement.writeTop,
statement.writeDistinct,
statement.writeStrings(" ", columnStr),
statement.writeFrom,
statement.writeWhereWithMssqlPagination,
statement.writeGroupBy,
statement.writeHaving,
statement.writeOrderBys,
statement.writeForUpdate,
)
}
func (statement *Statement) writeOracleLegacySelect(buf *builder.BytesWriter, columnStr string) error {
return statement.writeMultiple(buf,
statement.writeSelectColumns(columnStr),
statement.writeFrom,
statement.writeWhere,
statement.writeOracleLimit(columnStr),
statement.writeGroupBy,
statement.writeHaving,
statement.writeOrderBys,
statement.writeForUpdate,
)
}

View File

@ -0,0 +1,142 @@
// Copyright 2022 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 statements
import (
"errors"
"fmt"
"xorm.io/builder"
)
type orderBy struct {
orderStr interface{}
orderArgs []interface{}
direction string // ASC, DESC or "", "" means raw orderStr
}
func (ob orderBy) CheckValid() error {
if ob.orderStr == nil {
return fmt.Errorf("order by string is nil")
}
switch t := ob.orderStr.(type) {
case string:
if t == "" {
return fmt.Errorf("order by string is empty")
}
return nil
case *builder.Expression:
if t.Content() == "" {
return fmt.Errorf("order by string is empty")
}
return nil
default:
return fmt.Errorf("order by string is not string or builder.Expression")
}
}
func (statement *Statement) HasOrderBy() bool {
return len(statement.orderBy) > 0
}
// ResetOrderBy reset ordery conditions
func (statement *Statement) ResetOrderBy() {
statement.orderBy = []orderBy{}
}
var ErrNoColumnName = errors.New("no column name")
func (statement *Statement) writeOrderBy(w *builder.BytesWriter, orderBy orderBy) error {
switch t := orderBy.orderStr.(type) {
case *builder.Expression:
if _, err := fmt.Fprint(w.Builder, statement.dialect.Quoter().Replace(t.Content())); err != nil {
return err
}
w.Append(t.Args()...)
return nil
case string:
if orderBy.direction == "" {
if _, err := fmt.Fprint(w.Builder, statement.dialect.Quoter().Replace(t)); err != nil {
return err
}
w.Append(orderBy.orderArgs...)
return nil
}
if err := statement.dialect.Quoter().QuoteTo(w.Builder, t); err != nil {
return err
}
_, err := fmt.Fprint(w, " ", orderBy.direction)
return err
default:
return ErrUnSupportedSQLType
}
}
// WriteOrderBy write order by to writer
func (statement *Statement) writeOrderBys(w *builder.BytesWriter) error {
if len(statement.orderBy) == 0 {
return nil
}
if _, err := fmt.Fprint(w, " ORDER BY "); err != nil {
return err
}
for i, ob := range statement.orderBy {
if err := statement.writeOrderBy(w, ob); err != nil {
return err
}
if i < len(statement.orderBy)-1 {
if _, err := fmt.Fprint(w, ", "); err != nil {
return err
}
}
}
return nil
}
// OrderBy generate "Order By order" statement
func (statement *Statement) OrderBy(order interface{}, args ...interface{}) *Statement {
ob := orderBy{order, args, ""}
if err := ob.CheckValid(); err != nil {
statement.LastError = err
return statement
}
statement.orderBy = append(statement.orderBy, ob)
return statement
}
// Desc generate `ORDER BY xx DESC`
func (statement *Statement) Desc(colNames ...string) *Statement {
if len(colNames) == 0 {
statement.LastError = ErrNoColumnName
return statement
}
for _, colName := range colNames {
ob := orderBy{colName, nil, "DESC"}
statement.orderBy = append(statement.orderBy, ob)
if err := ob.CheckValid(); err != nil {
statement.LastError = err
return statement
}
}
return statement
}
// Asc provide asc order by query condition, the input parameters are columns.
func (statement *Statement) Asc(colNames ...string) *Statement {
if len(colNames) == 0 {
statement.LastError = ErrNoColumnName
return statement
}
for _, colName := range colNames {
ob := orderBy{colName, nil, "ASC"}
statement.orderBy = append(statement.orderBy, ob)
if err := ob.CheckValid(); err != nil {
statement.LastError = err
return statement
}
}
return statement
}

View File

@ -0,0 +1,142 @@
// Copyright 2023 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 statements
import (
"errors"
"fmt"
"xorm.io/builder"
"xorm.io/xorm/internal/utils"
"xorm.io/xorm/schemas"
)
func (statement *Statement) writePagination(bw *builder.BytesWriter) error {
dbType := statement.dialect.URI().DBType
if dbType == schemas.MSSQL || dbType == schemas.ORACLE {
return statement.writeOffsetFetch(bw)
}
return statement.writeLimitOffset(bw)
}
func (statement *Statement) writeLimitOffset(w builder.Writer) error {
if statement.Start > 0 {
if statement.LimitN != nil {
_, err := fmt.Fprintf(w, " LIMIT %v OFFSET %v", *statement.LimitN, statement.Start)
return err
}
_, err := fmt.Fprintf(w, " OFFSET %v", statement.Start)
return err
}
if statement.LimitN != nil {
_, err := fmt.Fprint(w, " LIMIT ", *statement.LimitN)
return err
}
// no limit statement
return nil
}
func (statement *Statement) writeOffsetFetch(w builder.Writer) error {
if statement.LimitN != nil {
_, err := fmt.Fprintf(w, " OFFSET %v ROWS FETCH NEXT %v ROWS ONLY", statement.Start, *statement.LimitN)
return err
}
if statement.Start > 0 {
_, err := fmt.Fprintf(w, " OFFSET %v ROWS", statement.Start)
return err
}
return nil
}
func (statement *Statement) writeWhereWithMssqlPagination(w *builder.BytesWriter) error {
if statement.cond.IsValid() {
if _, err := fmt.Fprint(w, " WHERE "); err != nil {
return err
}
if err := statement.cond.WriteTo(statement.QuoteReplacer(w)); err != nil {
return err
}
}
return statement.writeMssqlPaginationCond(w)
}
// write subquery to implement limit offset
// (mssql legacy only)
func (statement *Statement) writeMssqlPaginationCond(w *builder.BytesWriter) error {
if statement.Start <= 0 {
return nil
}
if statement.RefTable == nil {
return errors.New("unsupported query limit without reference table")
}
var column string
if len(statement.RefTable.PKColumns()) == 0 {
for _, index := range statement.RefTable.Indexes {
if len(index.Cols) == 1 {
column = index.Cols[0]
break
}
}
if len(column) == 0 {
column = statement.RefTable.ColumnsSeq()[0]
}
} else {
column = statement.RefTable.PKColumns()[0].Name
}
if statement.NeedTableName() {
if len(statement.TableAlias) > 0 {
column = fmt.Sprintf("%s.%s", statement.TableAlias, column)
} else {
column = fmt.Sprintf("%s.%s", statement.TableName(), column)
}
}
subWriter := builder.NewWriter()
if _, err := fmt.Fprintf(subWriter, "(%s NOT IN (SELECT TOP %d %s",
column, statement.Start, column); err != nil {
return err
}
if err := statement.writeFrom(subWriter); err != nil {
return err
}
if err := statement.writeWhere(subWriter); err != nil {
return err
}
if err := statement.writeOrderBys(subWriter); err != nil {
return err
}
if err := statement.writeGroupBy(subWriter); err != nil {
return err
}
if _, err := fmt.Fprint(subWriter, "))"); err != nil {
return err
}
if err := statement.writeWhereOrAnd(w, statement.cond.IsValid()); err != nil {
return err
}
return utils.WriteBuilder(w, subWriter)
}
func (statement *Statement) writeOracleLimit(columnStr string) func(w *builder.BytesWriter) error {
return func(w *builder.BytesWriter) error {
if statement.LimitN == nil {
return nil
}
oldString := w.String()
w.Reset()
rawColStr := columnStr
if rawColStr == "*" {
rawColStr = "at.*"
}
_, err := fmt.Fprintf(w, "SELECT %v FROM (SELECT %v,ROWNUM RN FROM (%v) at WHERE ROWNUM <= %d) aat WHERE RN > %d",
columnStr, rawColStr, oldString, statement.Start+*statement.LimitN, statement.Start)
return err
}
}

View File

@ -28,49 +28,15 @@ func (statement *Statement) GenQuerySQL(sqlOrArgs ...interface{}) (string, []int
return "", nil, ErrTableNotFound
}
var columnStr = statement.ColumnStr()
if len(statement.SelectStr) > 0 {
columnStr = statement.SelectStr
} else {
if statement.JoinStr == "" {
if columnStr == "" {
if statement.GroupByStr != "" {
columnStr = statement.quoteColumnStr(statement.GroupByStr)
} else {
columnStr = statement.genColumnStr()
}
}
} else {
if columnStr == "" {
if statement.GroupByStr != "" {
columnStr = statement.quoteColumnStr(statement.GroupByStr)
} else {
columnStr = "*"
}
}
}
if columnStr == "" {
columnStr = "*"
}
}
if err := statement.ProcessIDParam(); err != nil {
return "", nil, err
}
sqlStr, condArgs, err := statement.genSelectSQL(columnStr, true, true)
if err != nil {
buf := builder.NewWriter()
if err := statement.writeSelect(buf, statement.genSelectColumnStr(), false); err != nil {
return "", nil, err
}
args := append(statement.joinArgs, condArgs...)
// for mssql and use limit
qs := strings.Count(sqlStr, "?")
if len(args)*2 == qs {
args = append(args, args...)
}
return sqlStr, args, nil
return buf.String(), buf.Args(), nil
}
// GenSumSQL generates sum SQL
@ -79,9 +45,11 @@ func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (stri
return statement.GenRawSQL(), statement.RawParams, nil
}
statement.SetRefBean(bean)
if err := statement.SetRefBean(bean); err != nil {
return "", nil, err
}
var sumStrs = make([]string, 0, len(columns))
sumStrs := make([]string, 0, len(columns))
for _, colName := range columns {
if !strings.Contains(colName, " ") && !strings.Contains(colName, "(") {
colName = statement.quote(colName)
@ -90,18 +58,16 @@ func (statement *Statement) GenSumSQL(bean interface{}, columns ...string) (stri
}
sumStrs = append(sumStrs, fmt.Sprintf("COALESCE(sum(%s),0)", colName))
}
sumSelect := strings.Join(sumStrs, ", ")
if err := statement.mergeConds(bean); err != nil {
if err := statement.MergeConds(bean); err != nil {
return "", nil, err
}
sqlStr, condArgs, err := statement.genSelectSQL(sumSelect, true, true)
if err != nil {
buf := builder.NewWriter()
if err := statement.writeSelect(buf, strings.Join(sumStrs, ", "), true); err != nil {
return "", nil, err
}
return sqlStr, append(statement.joinArgs, condArgs...), nil
return buf.String(), buf.Args(), nil
}
// GenGetSQL generates Get SQL
@ -111,16 +77,18 @@ func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{},
v := rValue(bean)
isStruct = v.Kind() == reflect.Struct
if isStruct {
statement.SetRefBean(bean)
if err := statement.SetRefBean(bean); err != nil {
return "", nil, err
}
}
}
var columnStr = statement.ColumnStr()
columnStr := statement.ColumnStr()
if len(statement.SelectStr) > 0 {
columnStr = statement.SelectStr
} else {
// TODO: always generate column names, not use * even if join
if len(statement.JoinStr) == 0 {
if len(statement.joins) == 0 {
if len(columnStr) == 0 {
if len(statement.GroupByStr) > 0 {
columnStr = statement.quoteColumnStr(statement.GroupByStr)
@ -142,7 +110,7 @@ func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{},
}
if isStruct {
if err := statement.mergeConds(bean); err != nil {
if err := statement.MergeConds(bean); err != nil {
return "", nil, err
}
} else {
@ -151,12 +119,11 @@ func (statement *Statement) GenGetSQL(bean interface{}) (string, []interface{},
}
}
sqlStr, condArgs, err := statement.genSelectSQL(columnStr, true, true)
if err != nil {
buf := builder.NewWriter()
if err := statement.writeSelect(buf, columnStr, false); err != nil {
return "", nil, err
}
return sqlStr, append(statement.joinArgs, condArgs...), nil
return buf.String(), buf.Args(), nil
}
// GenCountSQL generates the SQL for counting
@ -165,16 +132,16 @@ func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interfa
return statement.GenRawSQL(), statement.RawParams, nil
}
var condArgs []interface{}
var err error
if len(beans) > 0 {
statement.SetRefBean(beans[0])
if err := statement.mergeConds(beans[0]); err != nil {
if err := statement.SetRefBean(beans[0]); err != nil {
return "", nil, err
}
if err := statement.MergeConds(beans[0]); err != nil {
return "", nil, err
}
}
var selectSQL = statement.SelectStr
selectSQL := statement.SelectStr
if len(selectSQL) <= 0 {
if statement.IsDistinct {
selectSQL = fmt.Sprintf("count(DISTINCT %s)", statement.ColumnStr())
@ -184,6 +151,14 @@ func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interfa
selectSQL = "count(*)"
}
}
buf := builder.NewWriter()
if statement.GroupByStr != "" {
if _, err := fmt.Fprintf(buf, "SELECT %s FROM (", selectSQL); err != nil {
return "", nil, err
}
}
var subQuerySelect string
if statement.GroupByStr != "" {
subQuerySelect = statement.GroupByStr
@ -191,149 +166,124 @@ func (statement *Statement) GenCountSQL(beans ...interface{}) (string, []interfa
subQuerySelect = selectSQL
}
sqlStr, condArgs, err := statement.genSelectSQL(subQuerySelect, false, false)
if err != nil {
if err := statement.writeSelect(buf, subQuerySelect, true); err != nil {
return "", nil, err
}
if statement.GroupByStr != "" {
sqlStr = fmt.Sprintf("SELECT %s FROM (%s) sub", selectSQL, sqlStr)
if _, err := fmt.Fprintf(buf, ") sub"); err != nil {
return "", nil, err
}
}
return sqlStr, append(statement.joinArgs, condArgs...), nil
return buf.String(), buf.Args(), nil
}
func (statement *Statement) genSelectSQL(columnStr string, needLimit, needOrderBy bool) (string, []interface{}, error) {
var (
distinct string
dialect = statement.dialect
quote = statement.quote
fromStr = " FROM "
top, mssqlCondi, whereStr string
func (statement *Statement) writeFrom(w *builder.BytesWriter) error {
return statement.writeMultiple(w,
statement.writeStrings(" FROM "),
statement.writeTableName,
statement.writeAlias,
statement.writeIndexHints,
statement.writeJoins,
)
if statement.IsDistinct && !strings.HasPrefix(columnStr, "count") {
distinct = "DISTINCT "
}
// write "TOP <n>" (mssql only)
func (statement *Statement) writeTop(w *builder.BytesWriter) error {
if statement.LimitN == nil {
return nil
}
_, err := fmt.Fprintf(w, " TOP %d", *statement.LimitN)
return err
}
func (statement *Statement) writeDistinct(w *builder.BytesWriter) error {
if statement.IsDistinct && !strings.HasPrefix(statement.SelectStr, "count(") {
_, err := fmt.Fprint(w, " DISTINCT")
return err
}
return nil
}
func (statement *Statement) writeSelectColumns(columnStr string) func(w *builder.BytesWriter) error {
return statement.groupWriteFns(
statement.writeStrings("SELECT"),
statement.writeDistinct,
statement.writeStrings(" ", columnStr),
)
}
func (statement *Statement) writeWhereCond(w *builder.BytesWriter, cond builder.Cond) error {
if !cond.IsValid() {
return nil
}
condSQL, condArgs, err := statement.GenCondSQL(statement.cond)
if err != nil {
return "", nil, err
if _, err := fmt.Fprint(w, " WHERE "); err != nil {
return err
}
if len(condSQL) > 0 {
whereStr = " WHERE " + condSQL
return cond.WriteTo(statement.QuoteReplacer(w))
}
func (statement *Statement) writeWhere(w *builder.BytesWriter) error {
return statement.writeWhereCond(w, statement.cond)
}
func (statement *Statement) writeForUpdate(w *builder.BytesWriter) error {
if !statement.IsForUpdate {
return nil
}
if dialect.URI().DBType == schemas.MSSQL && strings.Contains(statement.TableName(), "..") {
fromStr += statement.TableName()
} else {
fromStr += quote(statement.TableName())
if statement.dialect.URI().DBType != schemas.MYSQL {
return errors.New("only support mysql for update")
}
_, err := fmt.Fprint(w, " FOR UPDATE")
return err
}
if statement.TableAlias != "" {
if dialect.URI().DBType == schemas.ORACLE {
fromStr += " " + quote(statement.TableAlias)
} else {
fromStr += " AS " + quote(statement.TableAlias)
func (statement *Statement) writeSelect(buf *builder.BytesWriter, columnStr string, isCounting bool) error {
dbType := statement.dialect.URI().DBType
if statement.isUsingLegacyLimitOffset() {
if dbType == "mssql" {
return statement.writeMssqlLegacySelect(buf, columnStr)
}
}
if statement.JoinStr != "" {
fromStr = fmt.Sprintf("%v %v", fromStr, statement.JoinStr)
}
pLimitN := statement.LimitN
if dialect.URI().DBType == schemas.MSSQL {
if pLimitN != nil {
LimitNValue := *pLimitN
top = fmt.Sprintf("TOP %d ", LimitNValue)
}
if statement.Start > 0 {
if statement.RefTable == nil {
return "", nil, errors.New("Unsupported query limit without reference table")
}
var column string
if len(statement.RefTable.PKColumns()) == 0 {
for _, index := range statement.RefTable.Indexes {
if len(index.Cols) == 1 {
column = index.Cols[0]
break
}
}
if len(column) == 0 {
column = statement.RefTable.ColumnsSeq()[0]
}
} else {
column = statement.RefTable.PKColumns()[0].Name
}
if statement.needTableName() {
if len(statement.TableAlias) > 0 {
column = statement.TableAlias + "." + column
} else {
column = statement.TableName() + "." + column
}
}
var orderStr string
if needOrderBy && len(statement.OrderStr) > 0 {
orderStr = " ORDER BY " + statement.OrderStr
}
var groupStr string
if len(statement.GroupByStr) > 0 {
groupStr = " GROUP BY " + statement.GroupByStr
}
mssqlCondi = fmt.Sprintf("(%s NOT IN (SELECT TOP %d %s%s%s%s%s))",
column, statement.Start, column, fromStr, whereStr, orderStr, groupStr)
if dbType == "oracle" {
return statement.writeOracleLegacySelect(buf, columnStr)
}
}
var buf strings.Builder
fmt.Fprintf(&buf, "SELECT %v%v%v%v%v", distinct, top, columnStr, fromStr, whereStr)
if len(mssqlCondi) > 0 {
if len(whereStr) > 0 {
fmt.Fprint(&buf, " AND ", mssqlCondi)
} else {
fmt.Fprint(&buf, " WHERE ", mssqlCondi)
}
}
if statement.GroupByStr != "" {
fmt.Fprint(&buf, " GROUP BY ", statement.GroupByStr)
}
if statement.HavingStr != "" {
fmt.Fprint(&buf, " ", statement.HavingStr)
}
if needOrderBy && statement.OrderStr != "" {
fmt.Fprint(&buf, " ORDER BY ", statement.OrderStr)
}
if needLimit {
if dialect.URI().DBType != schemas.MSSQL && dialect.URI().DBType != schemas.ORACLE {
if statement.Start > 0 {
if pLimitN != nil {
fmt.Fprintf(&buf, " LIMIT %v OFFSET %v", *pLimitN, statement.Start)
} else {
fmt.Fprintf(&buf, "LIMIT 0 OFFSET %v", statement.Start)
return statement.writeMultiple(buf,
statement.writeSelectColumns(columnStr),
statement.writeFrom,
statement.writeWhere,
statement.writeGroupBy,
statement.writeHaving,
func(bw *builder.BytesWriter) (err error) {
if dbType == "mssql" && len(statement.orderBy) == 0 {
// ORDER BY is mandatory to use OFFSET and FETCH clause (only in sqlserver)
if statement.LimitN == nil && statement.Start == 0 {
// no need to add
return
}
} else if pLimitN != nil {
fmt.Fprint(&buf, " LIMIT ", *pLimitN)
}
} else if dialect.URI().DBType == schemas.ORACLE {
if statement.Start != 0 && pLimitN != nil {
oldString := buf.String()
buf.Reset()
rawColStr := columnStr
if rawColStr == "*" {
rawColStr = "at.*"
if statement.IsDistinct || len(statement.GroupByStr) > 0 || isCounting {
// the order-by column should be one of distincts or group-bys
// order by the first column
_, err = bw.WriteString(" ORDER BY 1 ASC")
return
}
fmt.Fprintf(&buf, "SELECT %v FROM (SELECT %v,ROWNUM RN FROM (%v) at WHERE ROWNUM <= %d) aat WHERE RN > %d",
columnStr, rawColStr, oldString, statement.Start+*pLimitN, statement.Start)
if statement.RefTable == nil || len(statement.RefTable.PrimaryKeys) != 1 {
// no primary key, order by the first column
_, err = bw.WriteString(" ORDER BY 1 ASC")
return
}
// order by primary key
statement.orderBy = []orderBy{{orderStr: statement.colName(statement.RefTable.GetColumn(statement.RefTable.PrimaryKeys[0]), statement.TableName()), direction: "ASC"}}
}
}
}
if statement.IsForUpdate {
return dialect.ForUpdateSQL(buf.String()), condArgs, nil
}
return buf.String(), condArgs, nil
return statement.writeOrderBys(bw)
},
statement.writePagination,
statement.writeForUpdate,
)
}
// GenExistSQL generates Exist SQL
@ -342,10 +292,6 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac
return statement.GenRawSQL(), statement.RawParams, nil
}
var sqlStr string
var args []interface{}
var joinStr string
var err error
var b interface{}
if len(bean) > 0 {
b = bean[0]
@ -364,45 +310,87 @@ func (statement *Statement) GenExistSQL(bean ...interface{}) (string, []interfac
if len(tableName) <= 0 {
return "", nil, ErrTableNotFound
}
if statement.RefTable == nil {
tableName = statement.quote(tableName)
if len(statement.JoinStr) > 0 {
joinStr = statement.JoinStr
}
if statement.RefTable != nil {
return statement.Limit(1).GenGetSQL(b)
}
tableName = statement.quote(tableName)
buf := builder.NewWriter()
if statement.dialect.URI().DBType == schemas.MSSQL {
if _, err := fmt.Fprintf(buf, "SELECT TOP 1 * FROM %s", tableName); err != nil {
return "", nil, err
}
if err := statement.writeJoins(buf); err != nil {
return "", nil, err
}
if err := statement.writeWhere(buf); err != nil {
return "", nil, err
}
} else if statement.dialect.URI().DBType == schemas.ORACLE {
if _, err := fmt.Fprintf(buf, "SELECT * FROM %s", tableName); err != nil {
return "", nil, err
}
if err := statement.writeJoins(buf); err != nil {
return "", nil, err
}
if _, err := fmt.Fprintf(buf, " WHERE "); err != nil {
return "", nil, err
}
if statement.Conds().IsValid() {
condSQL, condArgs, err := statement.GenCondSQL(statement.Conds())
if err != nil {
if err := statement.Conds().WriteTo(statement.QuoteReplacer(buf)); err != nil {
return "", nil, err
}
if statement.dialect.URI().DBType == schemas.MSSQL {
sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s WHERE %s", tableName, joinStr, condSQL)
} else if statement.dialect.URI().DBType == schemas.ORACLE {
sqlStr = fmt.Sprintf("SELECT * FROM %s WHERE (%s) %s AND ROWNUM=1", tableName, joinStr, condSQL)
} else {
sqlStr = fmt.Sprintf("SELECT * FROM %s %s WHERE %s LIMIT 1", tableName, joinStr, condSQL)
if _, err := fmt.Fprintf(buf, " AND "); err != nil {
return "", nil, err
}
args = condArgs
} else {
if statement.dialect.URI().DBType == schemas.MSSQL {
sqlStr = fmt.Sprintf("SELECT TOP 1 * FROM %s %s", tableName, joinStr)
} else if statement.dialect.URI().DBType == schemas.ORACLE {
sqlStr = fmt.Sprintf("SELECT * FROM %s %s WHERE ROWNUM=1", tableName, joinStr)
} else {
sqlStr = fmt.Sprintf("SELECT * FROM %s %s LIMIT 1", tableName, joinStr)
}
args = []interface{}{}
}
if _, err := fmt.Fprintf(buf, "ROWNUM=1"); err != nil {
return "", nil, err
}
} else {
statement.Limit(1)
sqlStr, args, err = statement.GenGetSQL(b)
if err != nil {
if _, err := fmt.Fprintf(buf, "SELECT 1 FROM %s", tableName); err != nil {
return "", nil, err
}
if err := statement.writeJoins(buf); err != nil {
return "", nil, err
}
if err := statement.writeWhere(buf); err != nil {
return "", nil, err
}
if _, err := fmt.Fprintf(buf, " LIMIT 1"); err != nil {
return "", nil, err
}
}
return sqlStr, args, nil
return buf.String(), buf.Args(), nil
}
func (statement *Statement) genSelectColumnStr() string {
// manually select columns
if len(statement.SelectStr) > 0 {
return statement.SelectStr
}
columnStr := statement.ColumnStr()
if columnStr != "" {
return columnStr
}
// autodetect columns
if statement.GroupByStr != "" {
return statement.quoteColumnStr(statement.GroupByStr)
}
if len(statement.joins) != 0 {
return "*"
}
columnStr = statement.genColumnStr()
if columnStr == "" {
columnStr = "*"
}
return columnStr
}
// GenFindSQL generates Find SQL
@ -411,52 +399,15 @@ func (statement *Statement) GenFindSQL(autoCond builder.Cond) (string, []interfa
return statement.GenRawSQL(), statement.RawParams, nil
}
var sqlStr string
var args []interface{}
var err error
if len(statement.TableName()) <= 0 {
return "", nil, ErrTableNotFound
}
var columnStr = statement.ColumnStr()
if len(statement.SelectStr) > 0 {
columnStr = statement.SelectStr
} else {
if statement.JoinStr == "" {
if columnStr == "" {
if statement.GroupByStr != "" {
columnStr = statement.quoteColumnStr(statement.GroupByStr)
} else {
columnStr = statement.genColumnStr()
}
}
} else {
if columnStr == "" {
if statement.GroupByStr != "" {
columnStr = statement.quoteColumnStr(statement.GroupByStr)
} else {
columnStr = "*"
}
}
}
if columnStr == "" {
columnStr = "*"
}
}
statement.cond = statement.cond.And(autoCond)
sqlStr, condArgs, err := statement.genSelectSQL(columnStr, true, true)
if err != nil {
buf := builder.NewWriter()
if err := statement.writeSelect(buf, statement.genSelectColumnStr(), false); err != nil {
return "", nil, err
}
args = append(statement.joinArgs, condArgs...)
// for mssql and use limit
qs := strings.Count(sqlStr, "?")
if len(args)*2 == qs {
args = append(args, args...)
}
return sqlStr, args, nil
return buf.String(), buf.Args(), nil
}

View File

@ -0,0 +1,137 @@
// Copyright 2022 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 statements
import (
"fmt"
"strings"
"xorm.io/xorm/schemas"
)
// Select replace select
func (statement *Statement) Select(str string) *Statement {
statement.SelectStr = statement.ReplaceQuote(str)
return statement
}
func col2NewCols(columns ...string) []string {
newColumns := make([]string, 0, len(columns))
for _, col := range columns {
col = strings.Replace(col, "`", "", -1)
col = strings.Replace(col, `"`, "", -1)
ccols := strings.Split(col, ",")
for _, c := range ccols {
newColumns = append(newColumns, strings.TrimSpace(c))
}
}
return newColumns
}
// Cols generate "col1, col2" statement
func (statement *Statement) Cols(columns ...string) *Statement {
cols := col2NewCols(columns...)
for _, nc := range cols {
statement.ColumnMap.Add(nc)
}
return statement
}
// ColumnStr returns column string
func (statement *Statement) ColumnStr() string {
return statement.dialect.Quoter().Join(statement.ColumnMap, ", ")
}
// AllCols update use only: update all columns
func (statement *Statement) AllCols() *Statement {
statement.useAllCols = true
return statement
}
// MustCols update use only: must update columns
func (statement *Statement) MustCols(columns ...string) *Statement {
newColumns := col2NewCols(columns...)
for _, nc := range newColumns {
statement.MustColumnMap[strings.ToLower(nc)] = true
}
return statement
}
// UseBool indicates that use bool fields as update contents and query contiditions
func (statement *Statement) UseBool(columns ...string) *Statement {
if len(columns) > 0 {
statement.MustCols(columns...)
} else {
statement.allUseBool = true
}
return statement
}
// Omit do not use the columns
func (statement *Statement) Omit(columns ...string) {
newColumns := col2NewCols(columns...)
for _, nc := range newColumns {
statement.OmitColumnMap = append(statement.OmitColumnMap, nc)
}
}
func (statement *Statement) genColumnStr() string {
if statement.RefTable == nil {
return ""
}
var buf strings.Builder
columns := statement.RefTable.Columns()
for _, col := range columns {
if statement.OmitColumnMap.Contain(col.Name) {
continue
}
if len(statement.ColumnMap) > 0 && !statement.ColumnMap.Contain(col.Name) {
continue
}
if col.MapType == schemas.ONLYTODB {
continue
}
if buf.Len() != 0 {
buf.WriteString(", ")
}
if len(statement.joins) > 0 {
if statement.TableAlias != "" {
buf.WriteString(statement.TableAlias)
} else {
buf.WriteString(statement.TableName())
}
buf.WriteString(".")
}
statement.dialect.Quoter().QuoteTo(&buf, col.Name)
}
return buf.String()
}
func (statement *Statement) colName(col *schemas.Column, tableName string) string {
if statement.NeedTableName() {
nm := tableName
if len(statement.TableAlias) > 0 {
nm = statement.TableAlias
}
return fmt.Sprintf("%s.%s", statement.quote(nm), statement.quote(col.Name))
}
return statement.quote(col.Name)
}
// Distinct generates "DISTINCT col1, col2 " statement
func (statement *Statement) Distinct(columns ...string) *Statement {
statement.IsDistinct = true
statement.Cols(columns...)
return statement
}

View File

@ -15,8 +15,8 @@ import (
"xorm.io/builder"
"xorm.io/xorm/contexts"
"xorm.io/xorm/convert"
"xorm.io/xorm/dialects"
"xorm.io/xorm/internal/convert"
"xorm.io/xorm/internal/json"
"xorm.io/xorm/internal/utils"
"xorm.io/xorm/schemas"
@ -34,6 +34,19 @@ var (
ErrTableNotFound = errors.New("Table not found")
)
type join struct {
op string
table interface{}
condition interface{}
args []interface{}
}
type indexHint struct {
op string
forType string
indexName string
}
// Statement save all the sql info for executing SQL
type Statement struct {
RefTable *schemas.Table
@ -43,9 +56,8 @@ type Statement struct {
Start int
LimitN *int
idParam schemas.PK
OrderStr string
JoinStr string
joinArgs []interface{}
orderBy []orderBy
joins []join
GroupByStr string
HavingStr string
SelectStr string
@ -78,6 +90,7 @@ type Statement struct {
BufferSize int
Context contexts.ContextCache
LastError error
indexHints []indexHint
}
// NewStatement creates a new statement
@ -101,15 +114,6 @@ func (statement *Statement) GenRawSQL() string {
return statement.ReplaceQuote(statement.RawSQL)
}
// GenCondSQL generates condition SQL
func (statement *Statement) GenCondSQL(condOrBuilder interface{}) (string, []interface{}, error) {
condSQL, condArgs, err := builder.ToSQL(condOrBuilder)
if err != nil {
return "", nil, err
}
return statement.ReplaceQuote(condSQL), condArgs, nil
}
// ReplaceQuote replace sql key words with quote
func (statement *Statement) ReplaceQuote(sql string) string {
if sql == "" || statement.dialect.URI().DBType == schemas.MYSQL ||
@ -129,10 +133,9 @@ func (statement *Statement) Reset() {
statement.RefTable = nil
statement.Start = 0
statement.LimitN = nil
statement.OrderStr = ""
statement.ResetOrderBy()
statement.UseCascade = true
statement.JoinStr = ""
statement.joinArgs = make([]interface{}, 0)
statement.joins = nil
statement.GroupByStr = ""
statement.HavingStr = ""
statement.ColumnMap = columnMap{}
@ -164,32 +167,17 @@ func (statement *Statement) Reset() {
statement.LastError = nil
}
// SetNoAutoCondition if you do not want convert bean's field as query condition, then use this function
func (statement *Statement) SetNoAutoCondition(no ...bool) *Statement {
statement.NoAutoCondition = true
if len(no) > 0 {
statement.NoAutoCondition = no[0]
}
return statement
}
// Alias set the table alias
func (statement *Statement) Alias(alias string) *Statement {
statement.TableAlias = alias
return statement
}
// SQL adds raw sql statement
func (statement *Statement) SQL(query interface{}, args ...interface{}) *Statement {
switch query.(type) {
case (*builder.Builder):
switch t := query.(type) {
case *builder.Builder:
var err error
statement.RawSQL, statement.RawParams, err = query.(*builder.Builder).ToSQL()
statement.RawSQL, statement.RawParams, err = t.ToSQL()
if err != nil {
statement.LastError = err
}
case string:
statement.RawSQL = query.(string)
statement.RawSQL = t
statement.RawParams = args
default:
statement.LastError = ErrUnSupportedSQLType
@ -198,80 +186,10 @@ func (statement *Statement) SQL(query interface{}, args ...interface{}) *Stateme
return statement
}
// Where add Where statement
func (statement *Statement) Where(query interface{}, args ...interface{}) *Statement {
return statement.And(query, args...)
}
func (statement *Statement) quote(s string) string {
return statement.dialect.Quoter().Quote(s)
}
// And add Where & and statement
func (statement *Statement) And(query interface{}, args ...interface{}) *Statement {
switch qr := query.(type) {
case string:
cond := builder.Expr(qr, args...)
statement.cond = statement.cond.And(cond)
case map[string]interface{}:
cond := make(builder.Eq)
for k, v := range qr {
cond[statement.quote(k)] = v
}
statement.cond = statement.cond.And(cond)
case builder.Cond:
statement.cond = statement.cond.And(qr)
for _, v := range args {
if vv, ok := v.(builder.Cond); ok {
statement.cond = statement.cond.And(vv)
}
}
default:
statement.LastError = ErrConditionType
}
return statement
}
// Or add Where & Or statement
func (statement *Statement) Or(query interface{}, args ...interface{}) *Statement {
switch qr := query.(type) {
case string:
cond := builder.Expr(qr, args...)
statement.cond = statement.cond.Or(cond)
case map[string]interface{}:
cond := make(builder.Eq)
for k, v := range qr {
cond[statement.quote(k)] = v
}
statement.cond = statement.cond.Or(cond)
case builder.Cond:
statement.cond = statement.cond.Or(qr)
for _, v := range args {
if vv, ok := v.(builder.Cond); ok {
statement.cond = statement.cond.Or(vv)
}
}
default:
statement.LastError = ErrConditionType
}
return statement
}
// In generate "Where column IN (?) " statement
func (statement *Statement) In(column string, args ...interface{}) *Statement {
in := builder.In(statement.quote(column), args...)
statement.cond = statement.cond.And(in)
return statement
}
// NotIn generate "Where column NOT IN (?) " statement
func (statement *Statement) NotIn(column string, args ...interface{}) *Statement {
notIn := builder.NotIn(statement.quote(column), args...)
statement.cond = statement.cond.And(notIn)
return statement
}
// SetRefValue set ref value
func (statement *Statement) SetRefValue(v reflect.Value) error {
var err error
@ -298,28 +216,8 @@ func (statement *Statement) SetRefBean(bean interface{}) error {
return nil
}
func (statement *Statement) needTableName() bool {
return len(statement.JoinStr) > 0
}
func (statement *Statement) colName(col *schemas.Column, tableName string) string {
if statement.needTableName() {
var nm = tableName
if len(statement.TableAlias) > 0 {
nm = statement.TableAlias
}
return statement.quote(nm) + "." + statement.quote(col.Name)
}
return statement.quote(col.Name)
}
// TableName return current tableName
func (statement *Statement) TableName() string {
if statement.AltTableName != "" {
return statement.AltTableName
}
return statement.tableName
func (statement *Statement) NeedTableName() bool {
return len(statement.joins) > 0
}
// Incr Generate "Update ... Set column = column + arg" statement
@ -352,85 +250,12 @@ func (statement *Statement) SetExpr(column string, expression interface{}) *Stat
return statement
}
// Distinct generates "DISTINCT col1, col2 " statement
func (statement *Statement) Distinct(columns ...string) *Statement {
statement.IsDistinct = true
statement.Cols(columns...)
return statement
}
// ForUpdate generates "SELECT ... FOR UPDATE" statement
func (statement *Statement) ForUpdate() *Statement {
statement.IsForUpdate = true
return statement
}
// Select replace select
func (statement *Statement) Select(str string) *Statement {
statement.SelectStr = statement.ReplaceQuote(str)
return statement
}
func col2NewCols(columns ...string) []string {
newColumns := make([]string, 0, len(columns))
for _, col := range columns {
col = strings.Replace(col, "`", "", -1)
col = strings.Replace(col, `"`, "", -1)
ccols := strings.Split(col, ",")
for _, c := range ccols {
newColumns = append(newColumns, strings.TrimSpace(c))
}
}
return newColumns
}
// Cols generate "col1, col2" statement
func (statement *Statement) Cols(columns ...string) *Statement {
cols := col2NewCols(columns...)
for _, nc := range cols {
statement.ColumnMap.Add(nc)
}
return statement
}
// ColumnStr returns column string
func (statement *Statement) ColumnStr() string {
return statement.dialect.Quoter().Join(statement.ColumnMap, ", ")
}
// AllCols update use only: update all columns
func (statement *Statement) AllCols() *Statement {
statement.useAllCols = true
return statement
}
// MustCols update use only: must update columns
func (statement *Statement) MustCols(columns ...string) *Statement {
newColumns := col2NewCols(columns...)
for _, nc := range newColumns {
statement.MustColumnMap[strings.ToLower(nc)] = true
}
return statement
}
// UseBool indicates that use bool fields as update contents and query contiditions
func (statement *Statement) UseBool(columns ...string) *Statement {
if len(columns) > 0 {
statement.MustCols(columns...)
} else {
statement.allUseBool = true
}
return statement
}
// Omit do not use the columns
func (statement *Statement) Omit(columns ...string) {
newColumns := col2NewCols(columns...)
for _, nc := range newColumns {
statement.OmitColumnMap = append(statement.OmitColumnMap, nc)
}
}
// Nullable Update use only: update columns to null when value is nullable and zero-value
func (statement *Statement) Nullable(columns ...string) {
newColumns := col2NewCols(columns...)
@ -454,54 +279,6 @@ func (statement *Statement) Limit(limit int, start ...int) *Statement {
return statement
}
// OrderBy generate "Order By order" statement
func (statement *Statement) OrderBy(order string) *Statement {
if len(statement.OrderStr) > 0 {
statement.OrderStr += ", "
}
statement.OrderStr += statement.ReplaceQuote(order)
return statement
}
// Desc generate `ORDER BY xx DESC`
func (statement *Statement) Desc(colNames ...string) *Statement {
var buf strings.Builder
if len(statement.OrderStr) > 0 {
fmt.Fprint(&buf, statement.OrderStr, ", ")
}
for i, col := range colNames {
if i > 0 {
fmt.Fprint(&buf, ", ")
}
statement.dialect.Quoter().QuoteTo(&buf, col)
fmt.Fprint(&buf, " DESC")
}
statement.OrderStr = buf.String()
return statement
}
// Asc provide asc order by query condition, the input parameters are columns.
func (statement *Statement) Asc(colNames ...string) *Statement {
var buf strings.Builder
if len(statement.OrderStr) > 0 {
fmt.Fprint(&buf, statement.OrderStr, ", ")
}
for i, col := range colNames {
if i > 0 {
fmt.Fprint(&buf, ", ")
}
statement.dialect.Quoter().QuoteTo(&buf, col)
fmt.Fprint(&buf, " ASC")
}
statement.OrderStr = buf.String()
return statement
}
// Conds returns condtions
func (statement *Statement) Conds() builder.Cond {
return statement.cond
}
// SetTable tempororily set table name, the parameter could be a string or a pointer of struct
func (statement *Statement) SetTable(tableNameOrBean interface{}) error {
v := rValue(tableNameOrBean)
@ -518,78 +295,34 @@ func (statement *Statement) SetTable(tableNameOrBean interface{}) error {
return nil
}
// Join The joinOP should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN
func (statement *Statement) Join(joinOP string, tablename interface{}, condition string, args ...interface{}) *Statement {
var buf strings.Builder
if len(statement.JoinStr) > 0 {
fmt.Fprintf(&buf, "%v %v JOIN ", statement.JoinStr, joinOP)
} else {
fmt.Fprintf(&buf, "%v JOIN ", joinOP)
}
switch tp := tablename.(type) {
case builder.Builder:
subSQL, subQueryArgs, err := tp.ToSQL()
if err != nil {
statement.LastError = err
return statement
}
fields := strings.Split(tp.TableName(), ".")
aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1])
aliasName = schemas.CommonQuoter.Trim(aliasName)
fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), aliasName, statement.ReplaceQuote(condition))
statement.joinArgs = append(statement.joinArgs, subQueryArgs...)
case *builder.Builder:
subSQL, subQueryArgs, err := tp.ToSQL()
if err != nil {
statement.LastError = err
return statement
}
fields := strings.Split(tp.TableName(), ".")
aliasName := statement.dialect.Quoter().Trim(fields[len(fields)-1])
aliasName = schemas.CommonQuoter.Trim(aliasName)
fmt.Fprintf(&buf, "(%s) %s ON %v", statement.ReplaceQuote(subSQL), aliasName, statement.ReplaceQuote(condition))
statement.joinArgs = append(statement.joinArgs, subQueryArgs...)
default:
tbName := dialects.FullTableName(statement.dialect, statement.tagParser.GetTableMapper(), tablename, true)
if !utils.IsSubQuery(tbName) {
var buf strings.Builder
statement.dialect.Quoter().QuoteTo(&buf, tbName)
tbName = buf.String()
}
fmt.Fprintf(&buf, "%s ON %v", tbName, statement.ReplaceQuote(condition))
}
statement.JoinStr = buf.String()
statement.joinArgs = append(statement.joinArgs, args...)
return statement
}
// tbNameNoSchema get some table's table name
func (statement *Statement) tbNameNoSchema(table *schemas.Table) string {
if len(statement.AltTableName) > 0 {
return statement.AltTableName
}
return table.Name
}
// GroupBy generate "Group By keys" statement
func (statement *Statement) GroupBy(keys string) *Statement {
statement.GroupByStr = statement.ReplaceQuote(keys)
return statement
}
func (statement *Statement) writeGroupBy(w *builder.BytesWriter) error {
if statement.GroupByStr == "" {
return nil
}
_, err := fmt.Fprint(w, " GROUP BY ", statement.GroupByStr)
return err
}
// Having generate "Having conditions" statement
func (statement *Statement) Having(conditions string) *Statement {
statement.HavingStr = fmt.Sprintf("HAVING %v", statement.ReplaceQuote(conditions))
statement.HavingStr = conditions
return statement
}
func (statement *Statement) writeHaving(w *builder.BytesWriter) error {
if statement.HavingStr == "" {
return nil
}
_, err := fmt.Fprint(w, " HAVING ", statement.ReplaceQuote(statement.HavingStr))
return err
}
// SetUnscoped always disable struct tag "deleted"
func (statement *Statement) SetUnscoped() *Statement {
statement.unscoped = true
@ -601,55 +334,6 @@ func (statement *Statement) GetUnscoped() bool {
return statement.unscoped
}
func (statement *Statement) genColumnStr() string {
if statement.RefTable == nil {
return ""
}
var buf strings.Builder
columns := statement.RefTable.Columns()
for _, col := range columns {
if statement.OmitColumnMap.Contain(col.Name) {
continue
}
if len(statement.ColumnMap) > 0 && !statement.ColumnMap.Contain(col.Name) {
continue
}
if col.MapType == schemas.ONLYTODB {
continue
}
if buf.Len() != 0 {
buf.WriteString(", ")
}
if statement.JoinStr != "" {
if statement.TableAlias != "" {
buf.WriteString(statement.TableAlias)
} else {
buf.WriteString(statement.TableName())
}
buf.WriteString(".")
}
statement.dialect.Quoter().QuoteTo(&buf, col.Name)
}
return buf.String()
}
// GenCreateTableSQL generated create table SQL
func (statement *Statement) GenCreateTableSQL() []string {
statement.RefTable.StoreEngine = statement.StoreEngine
statement.RefTable.Charset = statement.Charset
s, _ := statement.dialect.CreateTableSQL(statement.RefTable, statement.TableName())
return s
}
// GenIndexSQL generated create index SQL
func (statement *Statement) GenIndexSQL() []string {
var sqls []string
@ -734,7 +418,11 @@ func (statement *Statement) asDBCond(fieldValue reflect.Value, fieldType reflect
if !requiredField && (t.IsZero() || !fieldValue.IsValid()) {
return nil, false, nil
}
return dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t), true, nil
res, err := dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t)
if err != nil {
return nil, false, err
}
return res, true, nil
} else if fieldType.ConvertibleTo(schemas.BigFloatType) {
t := fieldValue.Convert(schemas.BigFloatType).Interface().(big.Float)
v := t.String()
@ -776,7 +464,7 @@ func (statement *Statement) asDBCond(fieldValue reflect.Value, fieldType reflect
if len(table.PrimaryKeys) == 1 {
pkField := reflect.Indirect(fieldValue).FieldByName(table.PKColumns()[0].FieldName)
// fix non-int pk issues
//if pkField.Int() != 0 {
// if pkField.Int() != 0 {
if pkField.IsValid() && !utils.IsZero(pkField.Interface()) {
return pkField.Interface(), true, nil
}
@ -825,7 +513,8 @@ func (statement *Statement) asDBCond(fieldValue reflect.Value, fieldType reflect
func (statement *Statement) buildConds2(table *schemas.Table, bean interface{},
includeVersion bool, includeUpdated bool, includeNil bool,
includeAutoIncr bool, allUseBool bool, useAllCols bool, unscoped bool,
mustColumnMap map[string]bool, tableName, aliasName string, addedTableName bool) (builder.Cond, error) {
mustColumnMap map[string]bool, tableName, aliasName string, addedTableName bool,
) (builder.Cond, error) {
var conds []builder.Cond
for _, col := range table.Columns() {
if !includeVersion && col.IsVersion {
@ -838,17 +527,13 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{},
continue
}
if statement.dialect.URI().DBType == schemas.MSSQL && (col.SQLType.Name == schemas.Text ||
col.SQLType.IsBlob() || col.SQLType.Name == schemas.TimeStampz) {
continue
}
if col.IsJSON {
continue
}
var colName string
if addedTableName {
var nm = tableName
nm := tableName
if len(aliasName) > 0 {
nm = aliasName
}
@ -859,9 +544,6 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{},
fieldValuePtr, err := col.ValueOf(bean)
if err != nil {
if !strings.Contains(err.Error(), "is not valid") {
//engine.logger.Warn(err)
}
continue
} else if fieldValuePtr == nil {
continue
@ -876,6 +558,15 @@ func (statement *Statement) buildConds2(table *schemas.Table, bean interface{},
continue
}
if statement.dialect.URI().DBType == schemas.MSSQL && (col.SQLType.Name == schemas.Text ||
col.SQLType.IsBlob() || col.SQLType.Name == schemas.TimeStampz) {
if utils.IsValueZero(fieldValue) {
continue
}
return nil, fmt.Errorf("column %s is a TEXT type with data %#v which cannot be as compare condition", col.Name, fieldValue.Interface())
}
requiredField := useAllCols
if b, ok := getFlagForColumn(mustColumnMap, col); ok {
if b {
@ -922,9 +613,10 @@ func (statement *Statement) BuildConds(table *schemas.Table, bean interface{}, i
statement.unscoped, statement.MustColumnMap, statement.TableName(), statement.TableAlias, addedTableName)
}
func (statement *Statement) mergeConds(bean interface{}) error {
// MergeConds merge conditions from bean and id
func (statement *Statement) MergeConds(bean interface{}) error {
if !statement.NoAutoCondition && statement.RefTable != nil {
var addedTableName = (len(statement.JoinStr) > 0)
addedTableName := len(statement.joins) > 0
autoCond, err := statement.BuildConds(statement.RefTable, bean, true, true, false, true, addedTableName)
if err != nil {
return err
@ -935,15 +627,6 @@ func (statement *Statement) mergeConds(bean interface{}) error {
return statement.ProcessIDParam()
}
// GenConds generates conditions
func (statement *Statement) GenConds(bean interface{}) (string, []interface{}, error) {
if err := statement.mergeConds(bean); err != nil {
return "", nil, err
}
return statement.GenCondSQL(statement.cond)
}
func (statement *Statement) quoteColumnStr(columnStr string) string {
columns := strings.Split(columnStr, ",")
return statement.dialect.Quoter().Join(columns, ",")
@ -962,12 +645,29 @@ func (statement *Statement) convertSQLOrArgs(sqlOrArgs ...interface{}) (string,
switch sqlOrArgs[0].(type) {
case string:
if len(sqlOrArgs) > 1 {
var newArgs = make([]interface{}, 0, len(sqlOrArgs)-1)
newArgs := make([]interface{}, 0, len(sqlOrArgs)-1)
for _, arg := range sqlOrArgs[1:] {
if v, ok := arg.(*time.Time); ok {
if v, ok := arg.(time.Time); ok {
newArgs = append(newArgs, v.In(statement.defaultTimeZone).Format("2006-01-02 15:04:05"))
} else if v, ok := arg.(time.Time); ok {
} else if v, ok := arg.(*time.Time); ok && v != nil {
newArgs = append(newArgs, v.In(statement.defaultTimeZone).Format("2006-01-02 15:04:05"))
} else if v, ok := arg.(convert.ConversionTo); ok {
r, err := v.ToDB()
if err != nil {
return "", nil, err
}
if r != nil {
// for nvarchar column on mssql, bytes have to be converted as ucs-2 external of driver
// for binary column, a string will be converted as bytes directly. So we have to
// convert bytes as string
if statement.dialect.URI().DBType == schemas.MSSQL {
newArgs = append(newArgs, string(r))
} else {
newArgs = append(newArgs, r)
}
} else {
newArgs = append(newArgs, nil)
}
} else {
newArgs = append(newArgs, arg)
}
@ -986,7 +686,7 @@ func (statement *Statement) convertSQLOrArgs(sqlOrArgs ...interface{}) (string,
}
func (statement *Statement) joinColumns(cols []*schemas.Column, includeTableName bool) string {
var colnames = make([]string, len(cols))
colnames := make([]string, len(cols))
for i, col := range cols {
if includeTableName {
colnames[i] = statement.quote(statement.TableName()) +
@ -1000,8 +700,8 @@ func (statement *Statement) joinColumns(cols []*schemas.Column, includeTableName
// CondDeleted returns the conditions whether a record is soft deleted.
func (statement *Statement) CondDeleted(col *schemas.Column) builder.Cond {
var colName = statement.quote(col.Name)
if statement.JoinStr != "" {
colName := statement.quote(col.Name)
if len(statement.joins) > 0 {
var prefix string
if statement.TableAlias != "" {
prefix = statement.TableAlias
@ -1010,14 +710,17 @@ func (statement *Statement) CondDeleted(col *schemas.Column) builder.Cond {
}
colName = statement.quote(prefix) + "." + statement.quote(col.Name)
}
var cond = builder.NewCond()
cond := builder.NewCond()
if col.SQLType.IsNumeric() {
cond = builder.Eq{colName: 0}
} else {
// FIXME: mssql: The conversion of a nvarchar data type to a datetime data type resulted in an out-of-range value.
if statement.dialect.URI().DBType != schemas.MSSQL {
cond = builder.Eq{colName: utils.ZeroTime1}
} else if col.SQLType.Name == schemas.TimeStamp || col.SQLType.Name == schemas.TimeStampz {
tmZone := statement.defaultTimeZone
if col.TimeZone != nil {
tmZone = col.TimeZone
}
cond = builder.Eq{colName: time.Unix(0, 0).In(tmZone).Format("2006-01-02 15:04:05.999999999")}
} else {
cond = builder.Eq{colName: utils.ZeroTime1}
}
if col.Nullable {

View File

@ -1,134 +0,0 @@
// Copyright 2019 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 statements
import (
"fmt"
"reflect"
"strings"
"time"
"xorm.io/builder"
"xorm.io/xorm/schemas"
)
func quoteNeeded(a interface{}) bool {
switch a.(type) {
case int, int8, int16, int32, int64:
return false
case uint, uint8, uint16, uint32, uint64:
return false
case float32, float64:
return false
case bool:
return false
case string:
return true
case time.Time, *time.Time:
return true
case builder.Builder, *builder.Builder:
return false
}
t := reflect.TypeOf(a)
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return false
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return false
case reflect.Float32, reflect.Float64:
return false
case reflect.Bool:
return false
case reflect.String:
return true
}
return true
}
func convertStringSingleQuote(arg string) string {
return "'" + strings.Replace(arg, "'", "''", -1) + "'"
}
func convertString(arg string) string {
var buf strings.Builder
buf.WriteRune('\'')
for _, c := range arg {
if c == '\\' || c == '\'' {
buf.WriteRune('\\')
}
buf.WriteRune(c)
}
buf.WriteRune('\'')
return buf.String()
}
func convertArg(arg interface{}, convertFunc func(string) string) string {
if quoteNeeded(arg) {
argv := fmt.Sprintf("%v", arg)
return convertFunc(argv)
}
return fmt.Sprintf("%v", arg)
}
const insertSelectPlaceHolder = true
// WriteArg writes an arg
func (statement *Statement) WriteArg(w *builder.BytesWriter, arg interface{}) error {
switch argv := arg.(type) {
case *builder.Builder:
if _, err := w.WriteString("("); err != nil {
return err
}
if err := argv.WriteTo(w); err != nil {
return err
}
if _, err := w.WriteString(")"); err != nil {
return err
}
default:
if insertSelectPlaceHolder {
if err := w.WriteByte('?'); err != nil {
return err
}
if v, ok := arg.(bool); ok && statement.dialect.URI().DBType == schemas.MSSQL {
if v {
w.Append(1)
} else {
w.Append(0)
}
} else {
w.Append(arg)
}
} else {
var convertFunc = convertStringSingleQuote
if statement.dialect.URI().DBType == schemas.MYSQL {
convertFunc = convertString
}
if _, err := w.WriteString(convertArg(arg, convertFunc)); err != nil {
return err
}
}
}
return nil
}
// WriteArgs writes args
func (statement *Statement) WriteArgs(w *builder.BytesWriter, args []interface{}) error {
for i, arg := range args {
if err := statement.WriteArg(w, arg); err != nil {
return err
}
if i+1 != len(args) {
if _, err := w.WriteString(","); err != nil {
return err
}
}
}
return nil
}

View File

@ -5,6 +5,7 @@
package statements
import (
"os"
"reflect"
"strings"
"testing"
@ -37,6 +38,7 @@ func TestMain(m *testing.M) {
panic("tags parser is nil")
}
m.Run()
os.Exit(0)
}
var colStrTests = []struct {
@ -77,6 +79,23 @@ func TestColumnsStringGeneration(t *testing.T) {
}
}
func TestConvertSQLOrArgs(t *testing.T) {
statement, err := createTestStatement()
assert.NoError(t, err)
// example orm struct
// type Table struct {
// ID int
// del *time.Time `xorm:"deleted"`
// }
args := []interface{}{
"INSERT `table` (`id`, `del`) VALUES (?, ?)", 1, (*time.Time)(nil),
}
// before fix, here will panic
_, _, err = statement.convertSQLOrArgs(args...)
assert.NoError(t, err)
}
func BenchmarkGetFlagForColumnWithICKey_ContainsKey(b *testing.B) {
b.StopTimer()

View File

@ -0,0 +1,56 @@
// Copyright 2022 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 statements
import (
"fmt"
"strings"
"xorm.io/builder"
"xorm.io/xorm/schemas"
)
// TableName return current tableName
func (statement *Statement) TableName() string {
if statement.AltTableName != "" {
return statement.AltTableName
}
return statement.tableName
}
// Alias set the table alias
func (statement *Statement) Alias(alias string) *Statement {
statement.TableAlias = alias
return statement
}
func (statement *Statement) writeAlias(w *builder.BytesWriter) error {
if statement.TableAlias != "" {
if statement.dialect.URI().DBType == schemas.ORACLE {
if _, err := fmt.Fprint(w, " ", statement.quote(statement.TableAlias)); err != nil {
return err
}
} else {
if _, err := fmt.Fprint(w, " AS ", statement.quote(statement.TableAlias)); err != nil {
return err
}
}
}
return nil
}
func (statement *Statement) writeTableName(w *builder.BytesWriter) error {
if statement.dialect.URI().DBType == schemas.MSSQL && strings.Contains(statement.TableName(), "..") {
if _, err := fmt.Fprint(w, statement.TableName()); err != nil {
return err
}
} else {
if _, err := fmt.Fprint(w, statement.quote(statement.TableName())); err != nil {
return err
}
}
return nil
}

View File

@ -11,15 +11,17 @@ import (
"reflect"
"time"
"xorm.io/builder"
"xorm.io/xorm/convert"
"xorm.io/xorm/dialects"
"xorm.io/xorm/internal/convert"
"xorm.io/xorm/internal/json"
"xorm.io/xorm/internal/utils"
"xorm.io/xorm/schemas"
)
func (statement *Statement) ifAddColUpdate(col *schemas.Column, includeVersion, includeUpdated, includeNil,
includeAutoIncr, update bool) (bool, error) {
includeAutoIncr, update bool,
) (bool, error) {
columnMap := statement.ColumnMap
omitColumnMap := statement.OmitColumnMap
unscoped := statement.unscoped
@ -64,15 +66,16 @@ func (statement *Statement) ifAddColUpdate(col *schemas.Column, includeVersion,
// BuildUpdates auto generating update columnes and values according a struct
func (statement *Statement) BuildUpdates(tableValue reflect.Value,
includeVersion, includeUpdated, includeNil,
includeAutoIncr, update bool) ([]string, []interface{}, error) {
includeAutoIncr, update bool,
) ([]string, []interface{}, error) {
table := statement.RefTable
allUseBool := statement.allUseBool
useAllCols := statement.useAllCols
mustColumnMap := statement.MustColumnMap
nullableMap := statement.NullableMap
var colNames = make([]string, 0)
var args = make([]interface{}, 0)
colNames := make([]string, 0)
args := make([]interface{}, 0)
for _, col := range table.Columns() {
ok, err := statement.ifAddColUpdate(col, includeVersion, includeUpdated, includeNil,
@ -123,6 +126,9 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value,
if fieldValue.CanAddr() {
if structConvert, ok := fieldValue.Addr().Interface().(convert.Conversion); ok {
if !includeNil && !requiredField && utils.IsZero(fieldValue.Interface()) {
continue
}
data, err := structConvert.ToDB()
if err != nil {
return nil, nil, err
@ -208,7 +214,10 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value,
if !requiredField && (t.IsZero() || !fieldValue.IsValid()) {
continue
}
val = dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t)
val, err = dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t)
if err != nil {
return nil, nil, err
}
} else if nulType, ok := fieldValue.Interface().(driver.Valuer); ok {
val, _ = nulType.Value()
if val == nil && !requiredField {
@ -303,3 +312,446 @@ func (statement *Statement) BuildUpdates(tableValue reflect.Value,
return colNames, args, nil
}
func (statement *Statement) writeUpdateTop(updateWriter *builder.BytesWriter) error {
if statement.dialect.URI().DBType != schemas.MSSQL || statement.LimitN == nil {
return nil
}
table := statement.RefTable
if statement.HasOrderBy() && table != nil && len(table.PrimaryKeys) == 1 {
return nil
}
_, err := fmt.Fprintf(updateWriter, " TOP (%d)", *statement.LimitN)
return err
}
func (statement *Statement) writeUpdateTableName(updateWriter *builder.BytesWriter) error {
tableName := statement.quote(statement.TableName())
if statement.TableAlias == "" {
_, err := fmt.Fprint(updateWriter, " ", tableName)
return err
}
switch statement.dialect.URI().DBType {
case schemas.MSSQL:
_, err := fmt.Fprint(updateWriter, " ", statement.TableAlias)
return err
default:
_, err := fmt.Fprint(updateWriter, " ", tableName, " AS ", statement.TableAlias)
return err
}
}
func (statement *Statement) writeUpdateFrom(updateWriter *builder.BytesWriter) (builder.Cond, error) {
if statement.dialect.URI().DBType == schemas.MSSQL {
if _, err := fmt.Fprint(updateWriter, " FROM"); err != nil {
return nil, err
}
if _, err := fmt.Fprint(updateWriter, " ", statement.quote(statement.TableName())); err != nil {
return nil, err
}
if statement.TableAlias != "" {
if _, err := fmt.Fprint(updateWriter, " ", statement.TableAlias); err != nil {
return nil, err
}
}
}
if len(statement.joins) == 0 {
return builder.NewCond(), nil
}
if statement.dialect.URI().DBType != schemas.MSSQL {
if _, err := fmt.Fprint(updateWriter, " FROM"); err != nil {
return nil, err
}
}
cond := builder.NewCond()
for i, join := range statement.joins {
if statement.dialect.URI().DBType == schemas.MSSQL || i > 0 {
if _, err := fmt.Fprint(updateWriter, ","); err != nil {
return nil, err
}
}
if err := statement.writeJoinTable(updateWriter, join); err != nil {
return nil, err
}
joinCond, err := statement.convertJoinCondition(join)
if err != nil {
return nil, err
}
cond = cond.And(joinCond)
}
return cond, nil
}
func (statement *Statement) writeWhereOrAnd(updateWriter *builder.BytesWriter, hasConditions bool) error {
if hasConditions {
_, err := fmt.Fprint(updateWriter, " AND ")
return err
}
_, err := fmt.Fprint(updateWriter, " WHERE ")
return err
}
func (statement *Statement) writeUpdateLimit(updateWriter *builder.BytesWriter, cond builder.Cond) error {
if statement.LimitN == nil {
return nil
}
table := statement.RefTable
tableName := statement.TableName()
limitValue := *statement.LimitN
switch statement.dialect.URI().DBType {
case schemas.MYSQL:
_, err := fmt.Fprintf(updateWriter, " LIMIT %d", limitValue)
return err
case schemas.SQLITE:
if err := statement.writeWhereOrAnd(updateWriter, cond.IsValid()); err != nil {
return err
}
if _, err := fmt.Fprint(updateWriter, "rowid IN (SELECT rowid FROM ", statement.quote(tableName)); err != nil {
return err
}
if err := statement.writeWhereCond(updateWriter, cond); err != nil {
return err
}
if err := statement.writeOrderBys(updateWriter); err != nil {
return err
}
_, err := fmt.Fprintf(updateWriter, " LIMIT %d)", limitValue)
return err
case schemas.POSTGRES:
if err := statement.writeWhereOrAnd(updateWriter, cond.IsValid()); err != nil {
return err
}
if _, err := fmt.Fprint(updateWriter, "CTID IN (SELECT CTID FROM ", statement.quote(tableName)); err != nil {
return err
}
if err := statement.writeWhereCond(updateWriter, cond); err != nil {
return err
}
if err := statement.writeOrderBys(updateWriter); err != nil {
return err
}
_, err := fmt.Fprintf(updateWriter, " LIMIT %d)", limitValue)
return err
case schemas.MSSQL:
if statement.HasOrderBy() && table != nil && len(table.PrimaryKeys) == 1 {
if _, err := fmt.Fprintf(updateWriter, " WHERE %s IN (SELECT TOP (%d) %s FROM %v",
table.PrimaryKeys[0], limitValue, table.PrimaryKeys[0],
statement.quote(tableName)); err != nil {
return err
}
if err := statement.writeWhereCond(updateWriter, cond); err != nil {
return err
}
if err := statement.writeOrderBys(updateWriter); err != nil {
return err
}
_, err := fmt.Fprint(updateWriter, ")")
return err
}
return nil
default: // TODO: Oracle support needed
return fmt.Errorf("not implemented")
}
}
func (statement *Statement) GenConditionsFromMap(m interface{}) ([]builder.Cond, error) {
switch t := m.(type) {
case map[string]interface{}:
conds := []builder.Cond{}
for k, v := range t {
conds = append(conds, builder.Eq{k: v})
}
return conds, nil
case map[string]string:
conds := []builder.Cond{}
for k, v := range t {
conds = append(conds, builder.Eq{k: v})
}
return conds, nil
default:
return nil, fmt.Errorf("unsupported condition map type %v", t)
}
}
func (statement *Statement) writeVersionIncrSet(w builder.Writer, v reflect.Value, hasPreviousSet bool) error {
if v.Type().Kind() != reflect.Struct {
return nil
}
table := statement.RefTable
if !(statement.RefTable != nil && table.Version != "" && statement.CheckVersion) {
return nil
}
verValue, err := table.VersionColumn().ValueOfV(&v)
if err != nil {
return err
}
if verValue == nil {
return nil
}
if hasPreviousSet {
if _, err := fmt.Fprint(w, ", "); err != nil {
return err
}
}
if _, err := fmt.Fprint(w, statement.quote(table.Version), " = ", statement.quote(table.Version), " + 1"); err != nil {
return err
}
return nil
}
func (statement *Statement) writeIncrSets(w builder.Writer, hasPreviousSets bool) error {
for i, expr := range statement.IncrColumns {
if i > 0 || hasPreviousSets {
if _, err := fmt.Fprint(w, ", "); err != nil {
return err
}
}
if _, err := fmt.Fprint(w, statement.quote(expr.ColName), " = ", statement.quote(expr.ColName), " + ?"); err != nil {
return err
}
w.Append(expr.Arg)
}
return nil
}
func (statement *Statement) writeDecrSets(w builder.Writer, hasPreviousSets bool) error {
// for update action to like "column = column - ?"
for i, expr := range statement.DecrColumns {
if i > 0 || hasPreviousSets {
if _, err := fmt.Fprint(w, ", "); err != nil {
return err
}
}
if _, err := fmt.Fprint(w, statement.quote(expr.ColName), " = ", statement.quote(expr.ColName), " - ?"); err != nil {
return err
}
w.Append(expr.Arg)
}
return nil
}
func (statement *Statement) writeExprSets(w *builder.BytesWriter, hasPreviousSets bool) error {
// for update action to like "column = expression"
for i, expr := range statement.ExprColumns {
if i > 0 || hasPreviousSets {
if _, err := fmt.Fprint(w, ", "); err != nil {
return err
}
}
switch tp := expr.Arg.(type) {
case string:
if len(tp) == 0 {
tp = "''"
}
if _, err := fmt.Fprint(w, statement.quote(expr.ColName), " = ", tp); err != nil {
return err
}
case *builder.Builder:
if _, err := fmt.Fprint(w, statement.quote(expr.ColName), " = ("); err != nil {
return err
}
if err := tp.WriteTo(statement.QuoteReplacer(w)); err != nil {
return err
}
if _, err := fmt.Fprint(w, ")"); err != nil {
return err
}
default:
if _, err := fmt.Fprint(w, statement.quote(expr.ColName), " = ?"); err != nil {
return err
}
w.Append(expr.Arg)
}
}
return nil
}
func (statement *Statement) writeSetColumns(colNames []string, args []interface{}) func(w *builder.BytesWriter) error {
return func(w *builder.BytesWriter) error {
if len(colNames) == 0 {
return nil
}
if len(colNames) != len(args) {
return fmt.Errorf("columns elements %d but args elements %d", len(colNames), len(args))
}
for i, colName := range colNames {
if i > 0 {
if _, err := fmt.Fprint(w, ", "); err != nil {
return err
}
}
if statement.dialect.URI().DBType != schemas.SQLITE && statement.dialect.URI().DBType != schemas.POSTGRES && len(statement.joins) > 0 {
tbName := statement.TableAlias
if tbName == "" {
tbName = statement.TableName()
}
if _, err := fmt.Fprint(w, tbName, ".", colName); err != nil {
return err
}
} else {
if _, err := fmt.Fprint(w, colName); err != nil {
return err
}
}
}
w.Append(args...)
return nil
}
}
func (statement *Statement) writeUpdateSets(w *builder.BytesWriter, v reflect.Value, colNames []string, args []interface{}) error {
// write set
if _, err := fmt.Fprint(w, " SET "); err != nil {
return err
}
previousLen := w.Len()
if err := statement.writeSetColumns(colNames, args)(w); err != nil {
return err
}
setNumber := len(colNames)
if err := statement.writeIncrSets(w, setNumber > 0); err != nil {
return err
}
setNumber += len(statement.IncrColumns)
if err := statement.writeDecrSets(w, setNumber > 0); err != nil {
return err
}
setNumber += len(statement.DecrColumns)
if err := statement.writeExprSets(w, setNumber > 0); err != nil {
return err
}
setNumber += len(statement.ExprColumns)
if err := statement.writeVersionIncrSet(w, v, setNumber > 0); err != nil {
return err
}
// if no columns to be updated, return error
if previousLen == w.Len() {
return ErrNoColumnsTobeUpdated
}
return nil
}
var ErrNoColumnsTobeUpdated = errors.New("no columns found to be updated")
func (statement *Statement) WriteUpdate(updateWriter *builder.BytesWriter, cond builder.Cond, v reflect.Value, colNames []string, args []interface{}) error {
switch statement.dialect.URI().DBType {
case schemas.MYSQL:
return statement.writeUpdateMySQL(updateWriter, cond, v, colNames, args)
case schemas.MSSQL:
return statement.writeUpdateMSSQL(updateWriter, cond, v, colNames, args)
default:
return statement.writeUpdateCommon(updateWriter, cond, v, colNames, args)
}
}
func (statement *Statement) writeUpdateMySQL(updateWriter *builder.BytesWriter, cond builder.Cond, v reflect.Value, colNames []string, args []interface{}) error {
if _, err := fmt.Fprintf(updateWriter, "UPDATE"); err != nil {
return err
}
if err := statement.writeUpdateTableName(updateWriter); err != nil {
return err
}
if err := statement.writeJoins(updateWriter); err != nil {
return err
}
if err := statement.writeUpdateSets(updateWriter, v, colNames, args); err != nil {
return err
}
// write where
if err := statement.writeWhereCond(updateWriter, cond); err != nil {
return err
}
if err := statement.writeOrderBys(updateWriter); err != nil {
return err
}
return statement.writeUpdateLimit(updateWriter, cond)
}
func (statement *Statement) writeUpdateMSSQL(updateWriter *builder.BytesWriter, cond builder.Cond, v reflect.Value, colNames []string, args []interface{}) error {
if _, err := fmt.Fprintf(updateWriter, "UPDATE"); err != nil {
return err
}
if err := statement.writeUpdateTop(updateWriter); err != nil {
return err
}
if err := statement.writeUpdateTableName(updateWriter); err != nil {
return err
}
if err := statement.writeUpdateSets(updateWriter, v, colNames, args); err != nil {
return err
}
// write from
joinConds, err := statement.writeUpdateFrom(updateWriter)
if err != nil {
return err
}
table := statement.RefTable
if statement.HasOrderBy() && table != nil && len(table.PrimaryKeys) == 1 {
} else {
// write where
if err := statement.writeWhereCond(updateWriter, cond.And(joinConds)); err != nil {
return err
}
}
return statement.writeUpdateLimit(updateWriter, cond.And(joinConds))
}
// writeUpdateCommon write update sql for non mysql && non mssql
func (statement *Statement) writeUpdateCommon(updateWriter *builder.BytesWriter, cond builder.Cond, v reflect.Value, colNames []string, args []interface{}) error {
if _, err := fmt.Fprintf(updateWriter, "UPDATE"); err != nil {
return err
}
if err := statement.writeUpdateTop(updateWriter); err != nil {
return err
}
if err := statement.writeUpdateTableName(updateWriter); err != nil {
return err
}
if err := statement.writeUpdateSets(updateWriter, v, colNames, args); err != nil {
return err
}
// write from
joinConds, err := statement.writeUpdateFrom(updateWriter)
if err != nil {
return err
}
// write where
if err := statement.writeWhereCond(updateWriter, cond.And(joinConds)); err != nil {
return err
}
return statement.writeUpdateLimit(updateWriter, cond.And(joinConds))
}

View File

@ -12,8 +12,8 @@ import (
"reflect"
"time"
"xorm.io/xorm/convert"
"xorm.io/xorm/dialects"
"xorm.io/xorm/internal/convert"
"xorm.io/xorm/internal/json"
"xorm.io/xorm/schemas"
)
@ -87,8 +87,21 @@ func (statement *Statement) Value2Interface(col *schemas.Column, fieldValue refl
case reflect.Struct:
if fieldType.ConvertibleTo(schemas.TimeType) {
t := fieldValue.Convert(schemas.TimeType).Interface().(time.Time)
tf := dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t)
return tf, nil
tf, err := dialects.FormatColumnTime(statement.dialect, statement.defaultTimeZone, col, t)
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 {

View File

@ -0,0 +1,37 @@
// Copyright 2023 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 statements
import (
"fmt"
"xorm.io/builder"
)
func (statement *Statement) writeStrings(strs ...string) func(w *builder.BytesWriter) error {
return func(w *builder.BytesWriter) error {
for _, str := range strs {
if _, err := fmt.Fprint(w, str); err != nil {
return err
}
}
return nil
}
}
func (statement *Statement) groupWriteFns(writeFuncs ...func(*builder.BytesWriter) error) func(*builder.BytesWriter) error {
return func(bw *builder.BytesWriter) error {
return statement.writeMultiple(bw, writeFuncs...)
}
}
func (statement *Statement) writeMultiple(buf *builder.BytesWriter, writeFuncs ...func(*builder.BytesWriter) error) (err error) {
for _, fn := range writeFuncs {
if err = fn(buf); err != nil {
return
}
}
return
}

27
internal/utils/builder.go Normal file
View File

@ -0,0 +1,27 @@
// Copyright 2022 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 utils
import (
"fmt"
"xorm.io/builder"
)
type BuildReader interface {
String() string
Args() []interface{}
}
// WriteBuilder writes writers to one
func WriteBuilder(w *builder.BytesWriter, inputs ...BuildReader) error {
for _, input := range inputs {
if _, err := fmt.Fprint(w, input.String()); err != nil {
return err
}
w.Append(input.Args()...)
}
return nil
}

View File

@ -6,9 +6,15 @@ package utils
import (
"fmt"
"strings"
)
// IndexName returns index name
func IndexName(tableName, idxName string) string {
return fmt.Sprintf("IDX_%v_%v", tableName, idxName)
}
// SeqName returns sequence name for some table
func SeqName(tableName string) string {
return "SEQ_" + strings.ToUpper(tableName)
}

25
internal/utils/new.go Normal file
View File

@ -0,0 +1,25 @@
// Copyright 2021 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 utils
import "reflect"
// New creates a value according type
func New(tp reflect.Type, length, cap int) reflect.Value {
switch tp.Kind() {
case reflect.Slice:
slice := reflect.MakeSlice(tp, length, cap)
x := reflect.New(slice.Type())
x.Elem().Set(slice)
return x
case reflect.Map:
mp := reflect.MakeMapWithSize(tp, cap)
x := reflect.New(mp.Type())
x.Elem().Set(mp)
return x
default:
return reflect.New(tp)
}
}

View File

@ -11,8 +11,8 @@ func SliceEq(left, right []string) bool {
if len(left) != len(right) {
return false
}
sort.Sort(sort.StringSlice(left))
sort.Sort(sort.StringSlice(right))
sort.Strings(left)
sort.Strings(right)
for i := 0; i < len(left); i++ {
if left[i] != right[i] {
return false
@ -20,3 +20,13 @@ func SliceEq(left, right []string) bool {
}
return true
}
// IndexSlice search c in slice s and return the index, return -1 if s don't contain c
func IndexSlice(s []string, c string) int {
for i, ss := range s {
if c == ss {
return i
}
}
return -1
}

View File

@ -146,6 +146,6 @@ const (
// IsTimeZero return true if a time is zero
func IsTimeZero(t time.Time) bool {
return t.IsZero() || t.Format("2006-01-02 15:04:05") == ZeroTime0 ||
t.Format("2006-01-02 15:04:05") == ZeroTime1
return t.IsZero() || t.Format("2006-01-02 15:04:05.999999999") == ZeroTime0 ||
t.Format("2006-01-02 15:04:05.999999999") == ZeroTime1
}

View File

@ -130,56 +130,56 @@ func NewSimpleLogger3(out io.Writer, prefix string, flag int, l LogLevel) *Simpl
// Error implement ILogger
func (s *SimpleLogger) Error(v ...interface{}) {
if s.level <= LOG_ERR {
s.ERR.Output(2, fmt.Sprintln(v...))
_ = s.ERR.Output(2, fmt.Sprintln(v...))
}
}
// Errorf implement ILogger
func (s *SimpleLogger) Errorf(format string, v ...interface{}) {
if s.level <= LOG_ERR {
s.ERR.Output(2, fmt.Sprintf(format, v...))
_ = s.ERR.Output(2, fmt.Sprintf(format, v...))
}
}
// Debug implement ILogger
func (s *SimpleLogger) Debug(v ...interface{}) {
if s.level <= LOG_DEBUG {
s.DEBUG.Output(2, fmt.Sprintln(v...))
_ = s.DEBUG.Output(2, fmt.Sprintln(v...))
}
}
// Debugf implement ILogger
func (s *SimpleLogger) Debugf(format string, v ...interface{}) {
if s.level <= LOG_DEBUG {
s.DEBUG.Output(2, fmt.Sprintf(format, v...))
_ = s.DEBUG.Output(2, fmt.Sprintf(format, v...))
}
}
// Info implement ILogger
func (s *SimpleLogger) Info(v ...interface{}) {
if s.level <= LOG_INFO {
s.INFO.Output(2, fmt.Sprintln(v...))
_ = s.INFO.Output(2, fmt.Sprintln(v...))
}
}
// Infof implement ILogger
func (s *SimpleLogger) Infof(format string, v ...interface{}) {
if s.level <= LOG_INFO {
s.INFO.Output(2, fmt.Sprintf(format, v...))
_ = s.INFO.Output(2, fmt.Sprintf(format, v...))
}
}
// Warn implement ILogger
func (s *SimpleLogger) Warn(v ...interface{}) {
if s.level <= LOG_WARNING {
s.WARN.Output(2, fmt.Sprintln(v...))
_ = s.WARN.Output(2, fmt.Sprintln(v...))
}
}
// Warnf implement ILogger
func (s *SimpleLogger) Warnf(format string, v ...interface{}) {
if s.level <= LOG_WARNING {
s.WARN.Output(2, fmt.Sprintf(format, v...))
_ = s.WARN.Output(2, fmt.Sprintf(format, v...))
}
}

View File

@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build !windows && !nacl && !plan9
// +build !windows,!nacl,!plan9
package log
@ -26,42 +27,42 @@ func NewSyslogLogger(w *syslog.Writer) *SyslogLogger {
// Debug log content as Debug
func (s *SyslogLogger) Debug(v ...interface{}) {
s.w.Debug(fmt.Sprint(v...))
_ = s.w.Debug(fmt.Sprint(v...))
}
// Debugf log content as Debug and format
func (s *SyslogLogger) Debugf(format string, v ...interface{}) {
s.w.Debug(fmt.Sprintf(format, v...))
_ = s.w.Debug(fmt.Sprintf(format, v...))
}
// Error log content as Error
func (s *SyslogLogger) Error(v ...interface{}) {
s.w.Err(fmt.Sprint(v...))
_ = s.w.Err(fmt.Sprint(v...))
}
// Errorf log content as Errorf and format
func (s *SyslogLogger) Errorf(format string, v ...interface{}) {
s.w.Err(fmt.Sprintf(format, v...))
_ = s.w.Err(fmt.Sprintf(format, v...))
}
// Info log content as Info
func (s *SyslogLogger) Info(v ...interface{}) {
s.w.Info(fmt.Sprint(v...))
_ = s.w.Info(fmt.Sprint(v...))
}
// Infof log content as Infof and format
func (s *SyslogLogger) Infof(format string, v ...interface{}) {
s.w.Info(fmt.Sprintf(format, v...))
_ = s.w.Info(fmt.Sprintf(format, v...))
}
// Warn log content as Warn
func (s *SyslogLogger) Warn(v ...interface{}) {
s.w.Warning(fmt.Sprint(v...))
_ = s.w.Warning(fmt.Sprint(v...))
}
// Warnf log content as Warnf and format
func (s *SyslogLogger) Warnf(format string, v ...interface{}) {
s.w.Warning(fmt.Sprintf(format, v...))
_ = s.w.Warning(fmt.Sprintf(format, v...))
}
// Level shows log level

View File

@ -1,10 +1,13 @@
package migrate
import (
"context"
"errors"
"fmt"
"reflect"
"xorm.io/xorm"
"xorm.io/xorm/schemas"
)
// MigrateFunc is the func signature for migrating.
@ -87,7 +90,11 @@ func (m *Migrate) Migrate() error {
return err
}
if m.initSchema != nil && m.isFirstRun() {
isFirstRun, err := m.isFirstRun()
if err != nil {
return err
}
if m.initSchema != nil && isFirstRun {
return m.runInitSchema()
}
@ -136,7 +143,9 @@ func (m *Migrate) RollbackMigration(mig *Migration) error {
return err
}
sql := fmt.Sprintf("DELETE FROM %s WHERE %s = ?", m.options.TableName, m.options.IDColumnName)
tableName := m.db.TableName(m.options.TableName, true)
sql := fmt.Sprintf("DELETE FROM %s WHERE %s = ?", tableName, m.options.IDColumnName)
if _, err := m.db.Exec(sql, mig.ID); err != nil {
return err
}
@ -188,7 +197,19 @@ func (m *Migrate) createMigrationTableIfNotExists() error {
return nil
}
sql := fmt.Sprintf("CREATE TABLE %s (%s VARCHAR(255) PRIMARY KEY)", m.options.TableName, m.options.IDColumnName)
idCol := schemas.NewColumn(m.options.IDColumnName, "", schemas.SQLType{
Name: "VARCHAR",
}, 255, 0, false)
idCol.IsPrimaryKey = true
table := schemas.NewTable(m.options.TableName, reflect.TypeOf(new(schemas.Table)))
table.AddColumn(idCol)
sql, _, err := m.db.Dialect().CreateTableSQL(context.Background(), m.db.DB(), table, m.options.TableName)
if err != nil {
return err
}
if _, err := m.db.Exec(sql); err != nil {
return err
}
@ -196,19 +217,21 @@ func (m *Migrate) createMigrationTableIfNotExists() error {
}
func (m *Migrate) migrationDidRun(mig *Migration) (bool, error) {
count, err := m.db.SQL(fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE %s = ?", m.options.TableName, m.options.IDColumnName), mig.ID).Count()
tableName := m.db.TableName(m.options.TableName, true)
count, err := m.db.SQL(fmt.Sprintf("SELECT COUNT(*) FROM %s WHERE %s = ?", tableName, m.options.IDColumnName), mig.ID).Count()
return count > 0, err
}
func (m *Migrate) isFirstRun() bool {
row := m.db.DB().QueryRow(fmt.Sprintf("SELECT COUNT(*) FROM %s", m.options.TableName))
func (m *Migrate) isFirstRun() (bool, error) {
var count int
row.Scan(&count)
return count == 0
tableName := m.db.TableName(m.options.TableName, true)
_, err := m.db.SQL(fmt.Sprintf("SELECT COUNT(*) FROM %s", tableName)).Get(&count)
return count == 0, err
}
func (m *Migrate) insertMigration(id string) error {
sql := fmt.Sprintf("INSERT INTO %s (%s) VALUES (?)", m.options.TableName, m.options.IDColumnName)
tableName := m.db.TableName(m.options.TableName, true)
sql := fmt.Sprintf("INSERT INTO %s (%s) VALUES (?)", tableName, m.options.IDColumnName)
_, err := m.db.Exec(sql, id)
return err
}

View File

@ -26,28 +26,26 @@ const (
dbName = "testdb.sqlite3"
)
var (
migrations = []*Migration{
{
ID: "201608301400",
Migrate: func(tx *xorm.Engine) error {
return tx.Sync2(&Person{})
},
Rollback: func(tx *xorm.Engine) error {
return tx.DropTables(&Person{})
},
var migrations = []*Migration{
{
ID: "201608301400",
Migrate: func(tx *xorm.Engine) error {
return tx.Sync(&Person{})
},
{
ID: "201608301430",
Migrate: func(tx *xorm.Engine) error {
return tx.Sync2(&Pet{})
},
Rollback: func(tx *xorm.Engine) error {
return tx.DropTables(&Pet{})
},
Rollback: func(tx *xorm.Engine) error {
return tx.DropTables(&Person{})
},
}
)
},
{
ID: "201608301430",
Migrate: func(tx *xorm.Engine) error {
return tx.Sync(&Pet{})
},
Rollback: func(tx *xorm.Engine) error {
return tx.DropTables(&Pet{})
},
},
}
func TestMigration(t *testing.T) {
_ = os.Remove(dbName)
@ -58,7 +56,7 @@ func TestMigration(t *testing.T) {
}
defer db.Close()
if err = db.DB().Ping(); err != nil {
if err = db.Ping(); err != nil {
log.Fatal(err)
}
@ -97,16 +95,16 @@ func TestInitSchema(t *testing.T) {
log.Fatal(err)
}
defer db.Close()
if err = db.DB().Ping(); err != nil {
if err = db.Ping(); err != nil {
log.Fatal(err)
}
m := New(db, DefaultOptions, migrations)
m.InitSchema(func(tx *xorm.Engine) error {
if err := tx.Sync2(&Person{}); err != nil {
if err := tx.Sync(&Person{}); err != nil {
return err
}
return tx.Sync2(&Pet{})
return tx.Sync(&Pet{})
})
err = m.Migrate()
@ -126,7 +124,7 @@ func TestMissingID(t *testing.T) {
if db != nil {
defer db.Close()
}
assert.NoError(t, db.DB().Ping())
assert.NoError(t, db.Ping())
migrationsMissingID := []*Migration{
{
@ -141,7 +139,6 @@ func TestMissingID(t *testing.T) {
}
func tableCount(db *xorm.Engine, tableName string) (count int) {
row := db.DB().QueryRow(fmt.Sprintf("SELECT COUNT(*) FROM %s", tableName))
row.Scan(&count)
_, _ = db.SQL(fmt.Sprintf("SELECT COUNT(*) FROM %s", tableName)).Get(&count)
return
}

View File

@ -149,7 +149,7 @@ func isASCIIUpper(r rune) bool {
func toASCIIUpper(r rune) rune {
if 'a' <= r && r <= 'z' {
r -= ('a' - 'A')
r -= 'a' - 'A'
}
return r
}

View File

@ -14,9 +14,15 @@ type TableName interface {
TableName() string
}
type TableComment interface {
TableComment() string
}
var (
tpTableName = reflect.TypeOf((*TableName)(nil)).Elem()
tvCache sync.Map
tpTableName = reflect.TypeOf((*TableName)(nil)).Elem()
tpTableComment = reflect.TypeOf((*TableComment)(nil)).Elem()
tvCache sync.Map
tcCache sync.Map
)
// GetTableName returns table name
@ -55,3 +61,40 @@ func GetTableName(mapper Mapper, v reflect.Value) string {
return mapper.Obj2Table(v.Type().Name())
}
// GetTableComment returns table comment
func GetTableComment(v reflect.Value) string {
if v.Type().Implements(tpTableComment) {
return v.Interface().(TableComment).TableComment()
}
if v.Kind() == reflect.Ptr {
v = v.Elem()
if v.Type().Implements(tpTableComment) {
return v.Interface().(TableComment).TableComment()
}
} else if v.CanAddr() {
v1 := v.Addr()
if v1.Type().Implements(tpTableComment) {
return v1.Interface().(TableComment).TableComment()
}
} else {
comment, ok := tcCache.Load(v.Type())
if ok {
if comment.(string) != "" {
return comment.(string)
}
} else {
v2 := reflect.New(v.Type())
if v2.Type().Implements(tpTableComment) {
tableComment := v2.Interface().(TableComment).TableComment()
tcCache.Store(v.Type(), tableComment)
return tableComment
}
tcCache.Store(v.Type(), "")
}
}
return ""
}

49
rows.go
View File

@ -11,7 +11,6 @@ import (
"xorm.io/builder"
"xorm.io/xorm/core"
"xorm.io/xorm/internal/utils"
)
// Rows rows wrapper a rows to
@ -41,14 +40,14 @@ func newRows(session *Session, bean interface{}) (*Rows, error) {
return nil, err
}
if len(session.statement.TableName()) <= 0 {
if len(session.statement.TableName()) == 0 {
return nil, ErrTableNotFound
}
if rows.session.statement.RawSQL == "" {
var autoCond builder.Cond
var addedTableName = (len(session.statement.JoinStr) > 0)
var table = rows.session.statement.RefTable
addedTableName := session.statement.NeedTableName()
table := rows.session.statement.RefTable
if !session.statement.NoAutoCondition {
var err error
@ -84,26 +83,41 @@ func newRows(session *Session, bean interface{}) (*Rows, error) {
// Next move cursor to next record, return false if end has reached
func (rows *Rows) Next() bool {
return rows.rows.Next()
if rows.rows != nil {
return rows.rows.Next()
}
return false
}
// Err returns the error, if any, that was encountered during iteration. Err may be called after an explicit or implicit Close.
func (rows *Rows) Err() error {
return rows.rows.Err()
if rows.rows != nil {
return rows.rows.Err()
}
return nil
}
// Scan row record to bean properties
func (rows *Rows) Scan(bean interface{}) error {
func (rows *Rows) Scan(beans ...interface{}) error {
if rows.Err() != nil {
return rows.Err()
}
if reflect.Indirect(reflect.ValueOf(bean)).Type() != rows.beanType {
return fmt.Errorf("scan arg is incompatible type to [%v]", rows.beanType)
bean := beans[0]
tp := reflect.TypeOf(bean)
if tp.Kind() == reflect.Ptr {
tp = tp.Elem()
}
beanKind := tp.Kind()
if err := rows.session.statement.SetRefBean(bean); err != nil {
return err
if len(beans) == 1 {
if reflect.Indirect(reflect.ValueOf(bean)).Type() != rows.beanType {
return fmt.Errorf("scan arg is incompatible type to [%v]", rows.beanType)
}
if err := rows.session.statement.SetRefBean(bean); err != nil {
return err
}
}
fields, err := rows.rows.Columns()
@ -115,14 +129,9 @@ func (rows *Rows) Scan(bean interface{}) error {
return err
}
scanResults, err := rows.session.row2Slice(rows.rows, fields, types, bean)
if err != nil {
return err
}
columnsSchema := ParseColumnsSchema(fields, types, rows.session.statement.RefTable)
dataStruct := utils.ReflectValue(bean)
_, err = rows.session.slice2Bean(scanResults, fields, bean, &dataStruct, rows.session.statement.RefTable)
if err != nil {
if err := rows.session.scan(rows.rows, rows.session.statement.RefTable, beanKind, beans, columnsSchema, types, fields); err != nil {
return err
}
@ -135,9 +144,11 @@ func (rows *Rows) Close() error {
defer rows.session.Close()
}
defer rows.session.resetStatement()
if rows.rows != nil {
return rows.rows.Close()
}
return rows.Err()
return nil
}

311
scan.go
View File

@ -11,9 +11,9 @@ import (
"reflect"
"time"
"xorm.io/xorm/convert"
"xorm.io/xorm/core"
"xorm.io/xorm/dialects"
"xorm.io/xorm/internal/convert"
"xorm.io/xorm/schemas"
)
@ -22,7 +22,7 @@ func genScanResultsByBeanNullable(bean interface{}) (interface{}, bool, error) {
switch t := bean.(type) {
case *interface{}:
return t, false, nil
case *sql.NullInt64, *sql.NullBool, *sql.NullFloat64, *sql.NullString, *sql.RawBytes:
case *sql.NullInt64, *sql.NullBool, *sql.NullFloat64, *sql.NullString, *sql.RawBytes, *[]byte:
return t, false, nil
case *time.Time:
return &sql.NullString{}, true, nil
@ -67,7 +67,7 @@ func genScanResultsByBeanNullable(bean interface{}) (interface{}, bool, error) {
case reflect.Uint32, reflect.Uint, reflect.Uint16, reflect.Uint8:
return &convert.NullUint32{}, true, nil
default:
return nil, false, fmt.Errorf("unsupported type: %#v", bean)
return nil, false, fmt.Errorf("genScanResultsByBeanNullable: unsupported type: %#v", bean)
}
}
@ -125,63 +125,12 @@ func genScanResultsByBean(bean interface{}) (interface{}, bool, error) {
case reflect.Float64:
return new(float64), true, nil
default:
return nil, false, fmt.Errorf("unsupported type: %#v", bean)
return nil, false, fmt.Errorf("genScanResultsByBean: unsupported type: %#v", bean)
}
}
func (engine *Engine) row2mapStr(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]string, error) {
var scanResults = make([]interface{}, len(fields))
for i := 0; i < len(fields); i++ {
var s sql.NullString
scanResults[i] = &s
}
if err := rows.Scan(scanResults...); err != nil {
return nil, err
}
result := make(map[string]string, len(fields))
for i, key := range fields {
s := scanResults[i].(*sql.NullString)
if s.String == "" {
result[key] = ""
continue
}
if schemas.TIME_TYPE == engine.dialect.ColumnTypeKind(types[i].DatabaseTypeName()) {
t, err := convert.String2Time(s.String, engine.DatabaseTZ, engine.TZLocation)
if err != nil {
return nil, err
}
result[key] = t.Format("2006-01-02 15:04:05")
} else {
result[key] = s.String
}
}
return result, nil
}
func row2mapBytes(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string][]byte, error) {
var scanResults = make([]interface{}, len(fields))
for i := 0; i < len(fields); i++ {
var s sql.NullString
scanResults[i] = &s
}
if err := rows.Scan(scanResults...); err != nil {
return nil, err
}
result := make(map[string][]byte, len(fields))
for ii, key := range fields {
s := scanResults[ii].(*sql.NullString)
result[key] = []byte(s.String)
}
return result, nil
}
func (engine *Engine) scanStringInterface(rows *core.Rows, fields []string, types []*sql.ColumnType) ([]interface{}, error) {
var scanResults = make([]interface{}, len(types))
scanResults := make([]interface{}, len(types))
for i := 0; i < len(types); i++ {
var s sql.NullString
scanResults[i] = &s
@ -195,8 +144,8 @@ func (engine *Engine) scanStringInterface(rows *core.Rows, fields []string, type
// scan is a wrap of driver.Scan but will automatically change the input values according requirements
func (engine *Engine) scan(rows *core.Rows, fields []string, types []*sql.ColumnType, vv ...interface{}) error {
var scanResults = make([]interface{}, 0, len(types))
var replaces = make([]bool, 0, len(types))
scanResults := make([]interface{}, 0, len(types))
replaces := make([]bool, 0, len(types))
var err error
for _, v := range vv {
var replaced bool
@ -245,7 +194,7 @@ func (engine *Engine) scan(rows *core.Rows, fields []string, types []*sql.Column
}
func (engine *Engine) scanInterfaces(rows *core.Rows, fields []string, types []*sql.ColumnType) ([]interface{}, error) {
var scanResultContainers = make([]interface{}, len(types))
scanResultContainers := make([]interface{}, len(types))
for i := 0; i < len(types); i++ {
scanResult, err := engine.driver.GenScanResult(types[i].DatabaseTypeName())
if err != nil {
@ -259,45 +208,12 @@ func (engine *Engine) scanInterfaces(rows *core.Rows, fields []string, types []*
return scanResultContainers, nil
}
func (engine *Engine) row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fields []string) ([]string, error) {
scanResults, err := engine.scanStringInterface(rows, fields, types)
if err != nil {
return nil, err
}
var results = make([]string, 0, len(fields))
for i := 0; i < len(fields); i++ {
results = append(results, scanResults[i].(*sql.NullString).String)
}
return results, nil
}
func rows2maps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) {
fields, err := rows.Columns()
if err != nil {
return nil, err
}
types, err := rows.ColumnTypes()
if err != nil {
return nil, err
}
for rows.Next() {
result, err := row2mapBytes(rows, types, fields)
if err != nil {
return nil, err
}
resultsSlice = append(resultsSlice, result)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return resultsSlice, nil
}
////////////////////
// row -> map[string]interface{}
func (engine *Engine) row2mapInterface(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]interface{}, error) {
var resultsMap = make(map[string]interface{}, len(fields))
var scanResultContainers = make([]interface{}, len(fields))
resultsMap := make(map[string]interface{}, len(fields))
scanResultContainers := make([]interface{}, len(fields))
for i := 0; i < len(fields); i++ {
scanResult, err := engine.driver.GenScanResult(types[i].DatabaseTypeName())
if err != nil {
@ -318,3 +234,206 @@ func (engine *Engine) row2mapInterface(rows *core.Rows, types []*sql.ColumnType,
}
return resultsMap, nil
}
// ScanInterfaceMap scan result from *core.Rows and return a map
func (engine *Engine) ScanInterfaceMap(rows *core.Rows) (map[string]interface{}, error) {
fields, err := rows.Columns()
if err != nil {
return nil, err
}
types, err := rows.ColumnTypes()
if err != nil {
return nil, err
}
return engine.row2mapInterface(rows, types, fields)
}
// ScanInterfaceMaps scan results from *core.Rows and return a slice of map
func (engine *Engine) ScanInterfaceMaps(rows *core.Rows) (resultsSlice []map[string]interface{}, err error) {
fields, err := rows.Columns()
if err != nil {
return nil, err
}
types, err := rows.ColumnTypes()
if err != nil {
return nil, err
}
for rows.Next() {
result, err := engine.row2mapInterface(rows, types, fields)
if err != nil {
return nil, err
}
resultsSlice = append(resultsSlice, result)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return resultsSlice, nil
}
////////////////////
// row -> map[string]string
func (engine *Engine) row2mapStr(rows *core.Rows, types []*sql.ColumnType, fields []string) (map[string]string, error) {
scanResults := make([]interface{}, len(fields))
for i := 0; i < len(fields); i++ {
var s sql.NullString
scanResults[i] = &s
}
if err := engine.driver.Scan(&dialects.ScanContext{
DBLocation: engine.DatabaseTZ,
UserLocation: engine.TZLocation,
}, rows, types, scanResults...); err != nil {
return nil, err
}
result := make(map[string]string, len(fields))
for i, key := range fields {
s := scanResults[i].(*sql.NullString)
if s.String == "" {
result[key] = ""
continue
}
if schemas.TIME_TYPE == engine.dialect.ColumnTypeKind(types[i].DatabaseTypeName()) {
t, err := convert.String2Time(s.String, engine.DatabaseTZ, engine.TZLocation)
if err != nil {
return nil, err
}
result[key] = t.Format("2006-01-02 15:04:05")
} else {
result[key] = s.String
}
}
return result, nil
}
// ScanStringMap scan results from *core.Rows and return a map
func (engine *Engine) ScanStringMap(rows *core.Rows) (map[string]string, error) {
fields, err := rows.Columns()
if err != nil {
return nil, err
}
types, err := rows.ColumnTypes()
if err != nil {
return nil, err
}
return engine.row2mapStr(rows, types, fields)
}
// ScanStringMaps scan results from *core.Rows and return a slice of map
func (engine *Engine) ScanStringMaps(rows *core.Rows) (resultsSlice []map[string]string, err error) {
fields, err := rows.Columns()
if err != nil {
return nil, err
}
types, err := rows.ColumnTypes()
if err != nil {
return nil, err
}
for rows.Next() {
result, err := engine.row2mapStr(rows, types, fields)
if err != nil {
return nil, err
}
resultsSlice = append(resultsSlice, result)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return resultsSlice, nil
}
////////////////////
// row -> map[string][]byte
func convertMapStr2Bytes(m map[string]string) map[string][]byte {
r := make(map[string][]byte, len(m))
for k, v := range m {
r[k] = []byte(v)
}
return r
}
func (engine *Engine) scanByteMaps(rows *core.Rows) (resultsSlice []map[string][]byte, err error) {
fields, err := rows.Columns()
if err != nil {
return nil, err
}
types, err := rows.ColumnTypes()
if err != nil {
return nil, err
}
for rows.Next() {
result, err := engine.row2mapStr(rows, types, fields)
if err != nil {
return nil, err
}
resultsSlice = append(resultsSlice, convertMapStr2Bytes(result))
}
if rows.Err() != nil {
return nil, rows.Err()
}
return resultsSlice, nil
}
////////////////////
// row -> []string
func (engine *Engine) row2sliceStr(rows *core.Rows, types []*sql.ColumnType, fields []string) ([]string, error) {
scanResults, err := engine.scanStringInterface(rows, fields, types)
if err != nil {
return nil, err
}
results := make([]string, 0, len(fields))
for i := 0; i < len(fields); i++ {
results = append(results, scanResults[i].(*sql.NullString).String)
}
return results, nil
}
// ScanStringSlice scan results from *core.Rows and return a slice of one row
func (engine *Engine) ScanStringSlice(rows *core.Rows) ([]string, error) {
fields, err := rows.Columns()
if err != nil {
return nil, err
}
types, err := rows.ColumnTypes()
if err != nil {
return nil, err
}
return engine.row2sliceStr(rows, types, fields)
}
// ScanStringSlices scan results from *core.Rows and return a slice of all rows
func (engine *Engine) ScanStringSlices(rows *core.Rows) (resultsSlice [][]string, err error) {
fields, err := rows.Columns()
if err != nil {
return nil, err
}
types, err := rows.ColumnTypes()
if err != nil {
return nil, err
}
for rows.Next() {
record, err := engine.row2sliceStr(rows, types, fields)
if err != nil {
return nil, err
}
resultsSlice = append(resultsSlice, record)
}
if rows.Err() != nil {
return nil, rows.Err()
}
return resultsSlice, nil
}

10
schemas/collation.go Normal file
View File

@ -0,0 +1,10 @@
// Copyright 2023 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 schemas
type Collation struct {
Name string
Column string // blank means it's a table collation
}

View File

@ -26,8 +26,9 @@ type Column struct {
FieldIndex []int // Available only when parsed from a struct
SQLType SQLType
IsJSON bool
Length int
Length2 int
IsJSONB bool
Length int64
Length2 int64
Nullable bool
Default string
Indexes map[string]int
@ -45,10 +46,11 @@ type Column struct {
DisableTimeZone bool
TimeZone *time.Location // column specified time zone
Comment string
Collation string
}
// NewColumn creates a new column
func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int, nullable bool) *Column {
func NewColumn(name, fieldName string, sqlType SQLType, len1, len2 int64, nullable bool) *Column {
return &Column{
Name: name,
IsJSON: sqlType.IsJson(),
@ -82,13 +84,15 @@ func (col *Column) ValueOf(bean interface{}) (*reflect.Value, error) {
// ValueOfV returns column's filed of struct's value accept reflevt value
func (col *Column) ValueOfV(dataStruct *reflect.Value) (*reflect.Value, error) {
var v = *dataStruct
v := *dataStruct
for _, i := range col.FieldIndex {
if v.Kind() == reflect.Ptr {
if v.IsNil() {
v.Set(reflect.New(v.Type().Elem()))
}
v = v.Elem()
} else if v.Kind() == reflect.Interface {
v = reflect.Indirect(v.Elem())
}
v = v.FieldByIndex([]int{i})
}

View File

@ -32,7 +32,7 @@ func NewIndex(name string, indexType int) *Index {
func (index *Index) XName(tableName string) string {
if !strings.HasPrefix(index.Name, "UQE_") &&
!strings.HasPrefix(index.Name, "IDX_") {
tableParts := strings.Split(strings.Replace(tableName, `"`, "", -1), ".")
tableParts := strings.Split(strings.ReplaceAll(tableName, `"`, ""), ".")
tableName = tableParts[len(tableParts)-1]
if index.Type == UniqueType {
return fmt.Sprintf("UQE_%v_%v", tableName, index.Name)

View File

@ -37,7 +37,7 @@ func (q Quoter) IsEmpty() bool {
// Quote quote a string
func (q Quoter) Quote(s string) string {
var buf strings.Builder
q.QuoteTo(&buf, s)
_ = q.QuoteTo(&buf, s)
return buf.String()
}
@ -64,7 +64,7 @@ func (q Quoter) Trim(s string) string {
// Join joins a slice with quoters
func (q Quoter) Join(a []string, sep string) string {
var b strings.Builder
q.JoinWrite(&b, a, sep)
_ = q.JoinWrite(&b, a, sep)
return b.String()
}
@ -86,7 +86,9 @@ func (q Quoter) JoinWrite(b *strings.Builder, a []string, sep string) error {
return err
}
}
q.QuoteTo(b, strings.TrimSpace(s))
if err := q.QuoteTo(b, strings.TrimSpace(s)); err != nil {
return err
}
}
return nil
}
@ -109,7 +111,7 @@ func findStart(value string, start int) int {
return start
}
var k = -1
k := -1
for j := start; j < len(value); j++ {
if value[j] != ' ' {
k = j
@ -120,8 +122,10 @@ func findStart(value string, start int) int {
return len(value)
}
if (value[k] == 'A' || value[k] == 'a') && (value[k+1] == 'S' || value[k+1] == 's') {
k = k + 2
if k+1 < len(value) &&
(value[k] == 'A' || value[k] == 'a') &&
(value[k+1] == 'S' || value[k+1] == 's') {
k += 2
}
for j := k; j < len(value); j++ {
@ -133,7 +137,7 @@ func findStart(value string, start int) int {
}
func (q Quoter) quoteWordTo(buf *strings.Builder, word string) error {
var realWord = word
realWord := word
if (word[0] == CommanQuoteMark && word[len(word)-1] == CommanQuoteMark) ||
(word[0] == q.Prefix && word[len(word)-1] == q.Suffix) {
realWord = word[1 : len(word)-1]
@ -161,17 +165,18 @@ func (q Quoter) quoteWordTo(buf *strings.Builder, word string) error {
}
// QuoteTo quotes the table or column names. i.e. if the quotes are [ and ]
// name -> [name]
// `name` -> [name]
// [name] -> [name]
// schema.name -> [schema].[name]
// `schema`.`name` -> [schema].[name]
// `schema`.name -> [schema].[name]
// schema.`name` -> [schema].[name]
// [schema].name -> [schema].[name]
// schema.[name] -> [schema].[name]
// name AS a -> [name] AS a
// schema.name AS a -> [schema].[name] AS a
//
// name -> [name]
// `name` -> [name]
// [name] -> [name]
// schema.name -> [schema].[name]
// `schema`.`name` -> [schema].[name]
// `schema`.name -> [schema].[name]
// schema.`name` -> [schema].[name]
// [schema].name -> [schema].[name]
// schema.[name] -> [schema].[name]
// name AS a -> [name] AS a
// schema.name AS a -> [schema].[name] AS a
func (q Quoter) QuoteTo(buf *strings.Builder, value string) error {
var i int
for i < len(value) {
@ -185,7 +190,7 @@ func (q Quoter) QuoteTo(buf *strings.Builder, value string) error {
return nil
}
var nextEnd = findWord(value, start)
nextEnd := findWord(value, start)
if err := q.quoteWordTo(buf, value[start:nextEnd]); err != nil {
return err
}
@ -196,7 +201,7 @@ func (q Quoter) QuoteTo(buf *strings.Builder, value string) error {
// Strings quotes a slice of string
func (q Quoter) Strings(s []string) []string {
var res = make([]string, 0, len(s))
res := make([]string, 0, len(s))
for _, a := range s {
res = append(res, q.Quote(a))
}
@ -215,7 +220,7 @@ func (q Quoter) Replace(sql string) string {
var beginSingleQuote bool
for i := 0; i < len(sql); i++ {
if !beginSingleQuote && sql[i] == CommanQuoteMark {
var j = i + 1
j := i + 1
for ; j < len(sql); j++ {
if sql[j] == CommanQuoteMark {
break

View File

@ -45,7 +45,8 @@ func TestAlwaysQuoteTo(t *testing.T) {
for _, v := range kases {
t.Run(v.value, func(t *testing.T) {
buf := &strings.Builder{}
quoter.QuoteTo(buf, v.value)
err := quoter.QuoteTo(buf, v.value)
assert.NoError(t, err)
assert.EqualValues(t, v.expected, buf.String())
})
}
@ -54,10 +55,7 @@ func TestAlwaysQuoteTo(t *testing.T) {
func TestReversedQuoteTo(t *testing.T) {
var (
quoter = Quoter{'[', ']', func(s string) bool {
if s == "mytable" {
return true
}
return false
return s == "mytable"
}}
kases = []struct {
expected string
@ -118,7 +116,8 @@ func TestNoQuoteTo(t *testing.T) {
for _, v := range kases {
t.Run(v.value, func(t *testing.T) {
buf := &strings.Builder{}
quoter.QuoteTo(buf, v.value)
err := quoter.QuoteTo(buf, v.value)
assert.NoError(t, err)
assert.EqualValues(t, v.expected, buf.String())
})
}
@ -132,6 +131,8 @@ func TestJoin(t *testing.T) {
assert.EqualValues(t, "[a].*,[b].[c]", quoter.Join([]string{"a.*", " b.c"}, ","))
assert.EqualValues(t, "[b] [a]", quoter.Join([]string{"b a"}, ","))
assert.EqualValues(t, "[f1], [f2], [f3]", quoter.Join(cols, ", "))
quoter.IsReserved = AlwaysNoReserve
@ -147,7 +148,7 @@ func TestStrings(t *testing.T) {
}
func TestTrim(t *testing.T) {
var kases = map[string]string{
kases := map[string]string{
"[table_name]": "table_name",
"[schema].[table_name]": "schema.table_name",
}
@ -160,7 +161,7 @@ func TestTrim(t *testing.T) {
func TestReplace(t *testing.T) {
q := Quoter{'[', ']', AlwaysReserve}
var kases = []struct {
kases := []struct {
source string
expected string
}{
@ -172,10 +173,18 @@ func TestReplace(t *testing.T) {
"SELECT 'abc```test```''', `a` FROM b",
"SELECT 'abc```test```''', [a] FROM b",
},
{
"SELECT * FROM `a` INNER JOIN `b` `c` WHERE `a`.`id` = `c`.`a_id`",
"SELECT * FROM [a] INNER JOIN [b] [c] WHERE [a].[id] = [c].[a_id]",
},
{
"UPDATE table SET `a` = ~ `a`, `b`='abc`'",
"UPDATE table SET [a] = ~ [a], [b]='abc`'",
},
{
"INSERT INTO `insert_where` (`height`,`name`,`repo_id`,`width`,`index`) SELECT $1,$2,$3,$4,coalesce(MAX(`index`),0)+1 FROM `insert_where` WHERE (`repo_id`=$5)",
"INSERT INTO [insert_where] ([height],[name],[repo_id],[width],[index]) SELECT $1,$2,$3,$4,coalesce(MAX([index]),0)+1 FROM [insert_where] WHERE ([repo_id]=$5)",
},
}
for _, kase := range kases {

View File

@ -27,6 +27,7 @@ type Table struct {
StoreEngine string
Charset string
Comment string
Collation string
}
// NewEmptyTable creates an empty table
@ -36,7 +37,8 @@ func NewEmptyTable() *Table {
// NewTable creates a new Table object
func NewTable(name string, t reflect.Type) *Table {
return &Table{Name: name, Type: t,
return &Table{
Name: name, Type: t,
columnsSeq: make([]string, 0),
columns: make([]*Column, 0),
columnsMap: make(map[string][]*Column),

View File

@ -5,9 +5,9 @@
package schemas
import (
"database/sql"
"math/big"
"reflect"
"sort"
"strings"
"time"
)
@ -22,13 +22,15 @@ const (
MYSQL DBType = "mysql"
MSSQL DBType = "mssql"
ORACLE DBType = "oracle"
DAMENG DBType = "dameng"
GBASE8S DBType = "gbase8s"
)
// SQLType represents SQL types
type SQLType struct {
Name string
DefaultLength int
DefaultLength2 int
DefaultLength int64
DefaultLength2 int64
}
// enumerates all columns types
@ -105,12 +107,14 @@ var (
Integer = "INTEGER"
BigInt = "BIGINT"
UnsignedBigInt = "UNSIGNED BIGINT"
Number = "NUMBER"
Enum = "ENUM"
Set = "SET"
Char = "CHAR"
Varchar = "VARCHAR"
VARCHAR2 = "VARCHAR2"
NChar = "NCHAR"
NVarchar = "NVARCHAR"
TinyText = "TINYTEXT"
@ -136,9 +140,10 @@ var (
Money = "MONEY"
SmallMoney = "SMALLMONEY"
Real = "REAL"
Float = "FLOAT"
Double = "DOUBLE"
Real = "REAL"
Float = "FLOAT"
UnsignedFloat = "UNSIGNED FLOAT"
Double = "DOUBLE"
Binary = "BINARY"
VarBinary = "VARBINARY"
@ -174,6 +179,7 @@ var (
Integer: NUMERIC_TYPE,
BigInt: NUMERIC_TYPE,
UnsignedBigInt: NUMERIC_TYPE,
Number: NUMERIC_TYPE,
Enum: TEXT_TYPE,
Set: TEXT_TYPE,
@ -185,6 +191,7 @@ var (
Char: TEXT_TYPE,
NChar: TEXT_TYPE,
Varchar: TEXT_TYPE,
VARCHAR2: TEXT_TYPE,
NVarchar: TEXT_TYPE,
TinyText: TEXT_TYPE,
Text: TEXT_TYPE,
@ -203,13 +210,14 @@ var (
SmallDateTime: TIME_TYPE,
Year: TIME_TYPE,
Decimal: NUMERIC_TYPE,
Numeric: NUMERIC_TYPE,
Real: NUMERIC_TYPE,
Float: NUMERIC_TYPE,
Double: NUMERIC_TYPE,
Money: NUMERIC_TYPE,
SmallMoney: NUMERIC_TYPE,
Decimal: NUMERIC_TYPE,
Numeric: NUMERIC_TYPE,
Real: NUMERIC_TYPE,
Float: NUMERIC_TYPE,
UnsignedFloat: NUMERIC_TYPE,
Double: NUMERIC_TYPE,
Money: NUMERIC_TYPE,
SmallMoney: NUMERIC_TYPE,
Binary: BLOB_TYPE,
VarBinary: BLOB_TYPE,
@ -227,90 +235,44 @@ var (
Serial: NUMERIC_TYPE,
BigSerial: NUMERIC_TYPE,
"INT8": NUMERIC_TYPE,
Array: ARRAY_TYPE,
}
intTypes = sort.StringSlice{"*int", "*int16", "*int32", "*int8"}
uintTypes = sort.StringSlice{"*uint", "*uint16", "*uint32", "*uint8"}
)
// !nashtsai! treat following var as interal const values, these are used for reflect.TypeOf comparison
var (
emptyString string
boolDefault bool
byteDefault byte
complex64Default complex64
complex128Default complex128
float32Default float32
float64Default float64
int64Default int64
uint64Default uint64
int32Default int32
uint32Default uint32
int16Default int16
uint16Default uint16
int8Default int8
uint8Default uint8
intDefault int
uintDefault uint
timeDefault time.Time
bigFloatDefault big.Float
)
// enumerates all types
var (
IntType = reflect.TypeOf(intDefault)
Int8Type = reflect.TypeOf(int8Default)
Int16Type = reflect.TypeOf(int16Default)
Int32Type = reflect.TypeOf(int32Default)
Int64Type = reflect.TypeOf(int64Default)
IntType = reflect.TypeOf((*int)(nil)).Elem()
Int8Type = reflect.TypeOf((*int8)(nil)).Elem()
Int16Type = reflect.TypeOf((*int16)(nil)).Elem()
Int32Type = reflect.TypeOf((*int32)(nil)).Elem()
Int64Type = reflect.TypeOf((*int64)(nil)).Elem()
UintType = reflect.TypeOf(uintDefault)
Uint8Type = reflect.TypeOf(uint8Default)
Uint16Type = reflect.TypeOf(uint16Default)
Uint32Type = reflect.TypeOf(uint32Default)
Uint64Type = reflect.TypeOf(uint64Default)
UintType = reflect.TypeOf((*uint)(nil)).Elem()
Uint8Type = reflect.TypeOf((*uint8)(nil)).Elem()
Uint16Type = reflect.TypeOf((*uint16)(nil)).Elem()
Uint32Type = reflect.TypeOf((*uint32)(nil)).Elem()
Uint64Type = reflect.TypeOf((*uint64)(nil)).Elem()
Float32Type = reflect.TypeOf(float32Default)
Float64Type = reflect.TypeOf(float64Default)
Float32Type = reflect.TypeOf((*float32)(nil)).Elem()
Float64Type = reflect.TypeOf((*float64)(nil)).Elem()
Complex64Type = reflect.TypeOf(complex64Default)
Complex128Type = reflect.TypeOf(complex128Default)
Complex64Type = reflect.TypeOf((*complex64)(nil)).Elem()
Complex128Type = reflect.TypeOf((*complex128)(nil)).Elem()
StringType = reflect.TypeOf(emptyString)
BoolType = reflect.TypeOf(boolDefault)
ByteType = reflect.TypeOf(byteDefault)
StringType = reflect.TypeOf((*string)(nil)).Elem()
BoolType = reflect.TypeOf((*bool)(nil)).Elem()
ByteType = reflect.TypeOf((*byte)(nil)).Elem()
BytesType = reflect.SliceOf(ByteType)
TimeType = reflect.TypeOf(timeDefault)
BigFloatType = reflect.TypeOf(bigFloatDefault)
)
// enumerates all types
var (
PtrIntType = reflect.PtrTo(IntType)
PtrInt8Type = reflect.PtrTo(Int8Type)
PtrInt16Type = reflect.PtrTo(Int16Type)
PtrInt32Type = reflect.PtrTo(Int32Type)
PtrInt64Type = reflect.PtrTo(Int64Type)
PtrUintType = reflect.PtrTo(UintType)
PtrUint8Type = reflect.PtrTo(Uint8Type)
PtrUint16Type = reflect.PtrTo(Uint16Type)
PtrUint32Type = reflect.PtrTo(Uint32Type)
PtrUint64Type = reflect.PtrTo(Uint64Type)
PtrFloat32Type = reflect.PtrTo(Float32Type)
PtrFloat64Type = reflect.PtrTo(Float64Type)
PtrComplex64Type = reflect.PtrTo(Complex64Type)
PtrComplex128Type = reflect.PtrTo(Complex128Type)
PtrStringType = reflect.PtrTo(StringType)
PtrBoolType = reflect.PtrTo(BoolType)
PtrByteType = reflect.PtrTo(ByteType)
PtrTimeType = reflect.PtrTo(TimeType)
TimeType = reflect.TypeOf((*time.Time)(nil)).Elem()
BigFloatType = reflect.TypeOf((*big.Float)(nil)).Elem()
NullFloat64Type = reflect.TypeOf((*sql.NullFloat64)(nil)).Elem()
NullStringType = reflect.TypeOf((*sql.NullString)(nil)).Elem()
NullInt32Type = reflect.TypeOf((*sql.NullInt32)(nil)).Elem()
NullInt64Type = reflect.TypeOf((*sql.NullInt64)(nil)).Elem()
NullBoolType = reflect.TypeOf((*sql.NullBool)(nil)).Elem()
)
// Type2SQLType generate SQLType acorrding Go's type
@ -331,7 +293,7 @@ func Type2SQLType(t reflect.Type) (st SQLType) {
case reflect.Complex64, reflect.Complex128:
st = SQLType{Varchar, 64, 0}
case reflect.Array, reflect.Slice, reflect.Map:
if t.Elem() == reflect.TypeOf(byteDefault) {
if t.Elem() == ByteType {
st = SQLType{Blob, 0, 0}
} else {
st = SQLType{Text, 0, 0}
@ -343,6 +305,16 @@ func Type2SQLType(t reflect.Type) (st SQLType) {
case reflect.Struct:
if t.ConvertibleTo(TimeType) {
st = SQLType{DateTime, 0, 0}
} else if t.ConvertibleTo(NullFloat64Type) {
st = SQLType{Double, 0, 0}
} else if t.ConvertibleTo(NullStringType) {
st = SQLType{Varchar, 255, 0}
} else if t.ConvertibleTo(NullInt32Type) {
st = SQLType{Integer, 0, 0}
} else if t.ConvertibleTo(NullInt64Type) {
st = SQLType{BigInt, 0, 0}
} else if t.ConvertibleTo(NullBoolType) {
st = SQLType{Boolean, 0, 0}
} else {
// TODO need to handle association struct
st = SQLType{Text, 0, 0}
@ -360,25 +332,29 @@ func SQLType2Type(st SQLType) reflect.Type {
name := strings.ToUpper(st.Name)
switch name {
case Bit, TinyInt, SmallInt, MediumInt, Int, Integer, Serial:
return reflect.TypeOf(1)
return IntType
case BigInt, BigSerial:
return reflect.TypeOf(int64(1))
return Int64Type
case UnsignedBit, UnsignedTinyInt, UnsignedSmallInt, UnsignedMediumInt, UnsignedInt:
return UintType
case UnsignedBigInt:
return Uint64Type
case Float, Real:
return reflect.TypeOf(float32(1))
return Float32Type
case Double:
return reflect.TypeOf(float64(1))
return Float64Type
case Char, NChar, Varchar, NVarchar, TinyText, Text, NText, MediumText, LongText, Enum, Set, Uuid, Clob, SysName:
return reflect.TypeOf("")
return StringType
case TinyBlob, Blob, LongBlob, Bytea, Binary, MediumBlob, VarBinary, UniqueIdentifier:
return reflect.TypeOf([]byte{})
return BytesType
case Bool:
return reflect.TypeOf(true)
return BoolType
case DateTime, Date, Time, TimeStamp, TimeStampz, SmallDateTime, Year:
return reflect.TypeOf(timeDefault)
return TimeType
case Decimal, Numeric, Money, SmallMoney:
return reflect.TypeOf("")
return StringType
default:
return reflect.TypeOf("")
return StringType
}
}

View File

@ -16,11 +16,9 @@ import (
"io"
"reflect"
"strconv"
"strings"
"xorm.io/xorm/contexts"
"xorm.io/xorm/convert"
"xorm.io/xorm/core"
"xorm.io/xorm/internal/convert"
"xorm.io/xorm/internal/json"
"xorm.io/xorm/internal/statements"
"xorm.io/xorm/log"
@ -34,7 +32,7 @@ type ErrFieldIsNotExist struct {
}
func (e ErrFieldIsNotExist) Error() string {
return fmt.Sprintf("field %s is not valid on table %s", e.FieldName, e.TableName)
return fmt.Sprintf("field %s is not exist on table %s", e.FieldName, e.TableName)
}
// ErrFieldIsNotValid is not valid
@ -79,7 +77,8 @@ type Session struct {
afterClosures []func(interface{})
afterProcessors []executedProcessor
stmtCache map[uint32]*core.Stmt //key: hash.Hash32 of (queryStr, len(queryStr))
stmtCache map[uint32]*core.Stmt // key: hash.Hash32 of (queryStr, len(queryStr))
txStmtCache map[uint32]*core.Stmt // for tx statement
lastSQL string
lastSQLArgs []interface{}
@ -123,13 +122,14 @@ func newSession(engine *Engine) *Session {
autoResetStatement: true,
prepareStmt: false,
afterInsertBeans: make(map[interface{}]*[]func(interface{}), 0),
afterUpdateBeans: make(map[interface{}]*[]func(interface{}), 0),
afterDeleteBeans: make(map[interface{}]*[]func(interface{}), 0),
afterInsertBeans: make(map[interface{}]*[]func(interface{})),
afterUpdateBeans: make(map[interface{}]*[]func(interface{})),
afterDeleteBeans: make(map[interface{}]*[]func(interface{})),
beforeClosures: make([]func(interface{}), 0),
afterClosures: make([]func(interface{}), 0),
afterProcessors: make([]executedProcessor, 0),
stmtCache: make(map[uint32]*core.Stmt),
txStmtCache: make(map[uint32]*core.Stmt),
lastSQL: "",
lastSQLArgs: make([]interface{}, 0),
@ -150,6 +150,12 @@ func (session *Session) Close() error {
}
}
for _, v := range session.txStmtCache {
if err := v.Close(); err != nil {
return err
}
}
if !session.isClosed {
// When Close be called, if session is a transaction and do not call
// Commit or Rollback, then call Rollback.
@ -160,6 +166,7 @@ func (session *Session) Close() error {
}
session.tx = nil
session.stmtCache = nil
session.txStmtCache = nil
session.isClosed = true
}
return nil
@ -174,6 +181,11 @@ func (session *Session) Engine() *Engine {
return session.engine
}
// Tx returns session tx
func (session *Session) Tx() *core.Tx {
return session.tx
}
func (session *Session) getQueryer() core.Queryer {
if session.tx != nil {
return session.tx
@ -195,6 +207,7 @@ func (session *Session) IsClosed() bool {
func (session *Session) resetStatement() {
if session.autoResetStatement {
session.statement.Reset()
session.prepareStmt = false
}
}
@ -260,8 +273,8 @@ func (session *Session) Limit(limit int, start ...int) *Session {
// OrderBy provide order by query condition, the input parameter is the content
// after order by on a sql statement.
func (session *Session) OrderBy(order string) *Session {
session.statement.OrderBy(order)
func (session *Session) OrderBy(order interface{}, args ...interface{}) *Session {
session.statement.OrderBy(order, args...)
return session
}
@ -299,7 +312,7 @@ func (session *Session) Cascade(trueOrFalse ...bool) *Session {
// MustLogSQL means record SQL or not and don't follow engine's setting
func (session *Session) MustLogSQL(logs ...bool) *Session {
var showSQL = true
showSQL := true
if len(logs) > 0 {
showSQL = logs[0]
}
@ -315,7 +328,7 @@ func (session *Session) NoCache() *Session {
}
// Join join_operator should be one of INNER, LEFT OUTER, CROSS etc - this will be prepended to JOIN
func (session *Session) Join(joinOperator string, tablename interface{}, condition string, args ...interface{}) *Session {
func (session *Session) Join(joinOperator string, tablename interface{}, condition interface{}, args ...interface{}) *Session {
session.statement.Join(joinOperator, tablename, condition, args...)
return session
}
@ -339,7 +352,7 @@ func (session *Session) DB() *core.DB {
func (session *Session) canCache() bool {
if session.statement.RefTable == nil ||
session.statement.JoinStr != "" ||
session.statement.NeedTableName() ||
session.statement.RawSQL != "" ||
!session.statement.UseCache ||
session.statement.IsForUpdate ||
@ -365,10 +378,25 @@ func (session *Session) doPrepare(db *core.DB, sqlStr string) (stmt *core.Stmt,
return
}
func (session *Session) getField(dataStruct *reflect.Value, table *schemas.Table, colName string, idx int) (*schemas.Column, *reflect.Value, error) {
var col = table.GetColumnIdx(colName, idx)
func (session *Session) doPrepareTx(sqlStr string) (stmt *core.Stmt, err error) {
crc := crc32.ChecksumIEEE([]byte(sqlStr))
// TODO try hash(sqlStr+len(sqlStr))
var has bool
stmt, has = session.txStmtCache[crc]
if !has {
stmt, err = session.tx.PrepareContext(session.ctx, sqlStr)
if err != nil {
return nil, err
}
session.txStmtCache[crc] = stmt
}
return
}
func getField(dataStruct *reflect.Value, table *schemas.Table, field *QueryedField) (*schemas.Column, *reflect.Value, error) {
col := field.ColumnSchema
if col == nil {
return nil, nil, ErrFieldIsNotExist{colName, table.Name}
return nil, nil, ErrFieldIsNotExist{field.FieldName, table.Name}
}
fieldValue, err := col.ValueOfV(dataStruct)
@ -376,10 +404,10 @@ func (session *Session) getField(dataStruct *reflect.Value, table *schemas.Table
return nil, nil, err
}
if fieldValue == nil {
return nil, nil, ErrFieldIsNotValid{colName, table.Name}
return nil, nil, ErrFieldIsNotValid{field.FieldName, table.Name}
}
if !fieldValue.IsValid() || !fieldValue.CanSet() {
return nil, nil, ErrFieldIsNotValid{colName, table.Name}
return nil, nil, ErrFieldIsNotValid{field.FieldName, table.Name}
}
return col, fieldValue, nil
@ -388,11 +416,12 @@ func (session *Session) getField(dataStruct *reflect.Value, table *schemas.Table
// Cell cell is a result of one column field
type Cell *interface{}
func (session *Session) rows2Beans(rows *core.Rows, fields []string, types []*sql.ColumnType,
func (session *Session) rows2Beans(rows *core.Rows, columnsSchema *ColumnsSchema, fields []string, types []*sql.ColumnType,
table *schemas.Table, newElemFunc func([]string) reflect.Value,
sliceValueSetFunc func(*reflect.Value, schemas.PK) error) error {
sliceValueSetFunc func(*reflect.Value, schemas.PK) error,
) error {
for rows.Next() {
var newValue = newElemFunc(fields)
newValue := newElemFunc(fields)
bean := newValue.Interface()
dataStruct := newValue.Elem()
@ -401,7 +430,7 @@ func (session *Session) rows2Beans(rows *core.Rows, fields []string, types []*sq
if err != nil {
return err
}
pk, err := session.slice2Bean(scanResults, fields, bean, &dataStruct, table)
pk, err := session.slice2Bean(scanResults, columnsSchema, fields, bean, &dataStruct, table)
if err != nil {
return err
}
@ -435,7 +464,7 @@ func (session *Session) row2Slice(rows *core.Rows, fields []string, types []*sql
return scanResults, nil
}
func (session *Session) setJSON(fieldValue *reflect.Value, fieldType reflect.Type, scanResult interface{}) error {
func setJSON(fieldValue *reflect.Value, fieldType reflect.Type, scanResult interface{}) error {
bs, ok := convert.AsBytes(scanResult)
if !ok {
return fmt.Errorf("unsupported database data type: %#v", scanResult)
@ -503,8 +532,11 @@ func asKind(vv reflect.Value, tp reflect.Type) (interface{}, error) {
return nil, fmt.Errorf("unsupported primary key type: %v, %v", tp, vv)
}
var uint8ZeroValue = reflect.ValueOf(uint8(0))
func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflect.Value,
scanResult interface{}, table *schemas.Table) error {
scanResult interface{}, table *schemas.Table,
) error {
v, ok := scanResult.(*interface{})
if ok {
scanResult = *v
@ -519,6 +551,9 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec
if !ok {
return fmt.Errorf("cannot convert %#v as bytes", scanResult)
}
if data == nil {
return nil
}
return structConvert.FromDB(data)
}
}
@ -543,7 +578,7 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec
fieldType := fieldValue.Type()
if col.IsJSON {
return session.setJSON(fieldValue, fieldType, scanResult)
return setJSON(fieldValue, fieldType, scanResult)
}
switch fieldType.Kind() {
@ -562,8 +597,8 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec
}
return nil
case reflect.Complex64, reflect.Complex128:
return session.setJSON(fieldValue, fieldType, scanResult)
case reflect.Slice, reflect.Array:
return setJSON(fieldValue, fieldType, scanResult)
case reflect.Slice:
bs, ok := convert.AsBytes(scanResult)
if ok && fieldType.Elem().Kind() == reflect.Uint8 {
if col.SQLType.IsText() {
@ -574,15 +609,29 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec
}
fieldValue.Set(x.Elem())
} else {
if fieldValue.Len() > 0 {
for i := 0; i < fieldValue.Len(); i++ {
if i < vv.Len() {
fieldValue.Index(i).Set(vv.Index(i))
}
}
} else {
for i := 0; i < vv.Len(); i++ {
fieldValue.Set(reflect.Append(*fieldValue, vv.Index(i)))
fieldValue.Set(reflect.ValueOf(bs))
}
return nil
}
case reflect.Array:
bs, ok := convert.AsBytes(scanResult)
if ok && fieldType.Elem().Kind() == reflect.Uint8 {
if col.SQLType.IsText() {
x := reflect.New(fieldType)
err := json.DefaultJSONHandler.Unmarshal(bs, x.Interface())
if err != nil {
return err
}
fieldValue.Set(x.Elem())
} else {
if fieldValue.Len() < vv.Len() {
return fmt.Errorf("Set field %s[Array] failed because of data too long", col.Name)
}
for i := 0; i < fieldValue.Len(); i++ {
if i < vv.Len() {
fieldValue.Index(i).Set(vv.Index(i))
} else {
fieldValue.Index(i).Set(uint8ZeroValue)
}
}
}
@ -626,7 +675,7 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec
if len(table.PrimaryKeys) != 1 {
return errors.New("unsupported non or composited primary key cascade")
}
var pk = make(schemas.PK, len(table.PrimaryKeys))
pk := make(schemas.PK, len(table.PrimaryKeys))
pk[0], err = asKind(vv, reflect.TypeOf(scanResult))
if err != nil {
return err
@ -654,34 +703,22 @@ func (session *Session) convertBeanField(col *schemas.Column, fieldValue *reflec
return convert.AssignValue(fieldValue.Addr(), scanResult)
}
func (session *Session) slice2Bean(scanResults []interface{}, fields []string, bean interface{}, dataStruct *reflect.Value, table *schemas.Table) (schemas.PK, error) {
func (session *Session) slice2Bean(scanResults []interface{}, columnsSchema *ColumnsSchema, fields []string, bean interface{}, dataStruct *reflect.Value, table *schemas.Table) (schemas.PK, error) {
defer func() {
executeAfterSet(bean, fields, scanResults)
}()
buildAfterProcessors(session, bean)
var tempMap = make(map[string]int)
var pk schemas.PK
for i, colName := range fields {
var idx int
var lKey = strings.ToLower(colName)
var ok bool
if idx, ok = tempMap[lKey]; !ok {
idx = 0
} else {
idx = idx + 1
}
tempMap[lKey] = idx
col, fieldValue, err := session.getField(dataStruct, table, colName, idx)
if err != nil {
if _, ok := err.(ErrFieldIsNotValid); !ok {
session.engine.logger.Warnf("%v", err)
}
for i, field := range columnsSchema.Fields {
col, fieldValue, err := getField(dataStruct, table, field)
if _, ok := err.(ErrFieldIsNotExist); ok {
continue
} else if err != nil {
return nil, err
}
if fieldValue == nil {
continue
}
@ -724,6 +761,12 @@ func (session *Session) incrVersionFieldValue(fieldValue *reflect.Value) {
// Context sets the context on this session
func (session *Session) Context(ctx context.Context) *Session {
if session.engine.logSessionID && session.ctx != nil {
ctx = context.WithValue(ctx, log.SessionIDKey, session.ctx.Value(log.SessionIDKey))
ctx = context.WithValue(ctx, log.SessionKey, session.ctx.Value(log.SessionKey))
ctx = context.WithValue(ctx, log.SessionShowSQLKey, session.ctx.Value(log.SessionShowSQLKey))
}
session.ctx = ctx
return session
}
@ -737,3 +780,13 @@ func (session *Session) PingContext(ctx context.Context) error {
session.engine.logger.Infof("PING DATABASE %v", session.engine.DriverName())
return session.DB().PingContext(ctx)
}
// disable version check
func (session *Session) NoVersionCheck() *Session {
session.statement.CheckVersion = false
return session
}
func SetDefaultJSONHandler(jsonHandler json.Interface) {
json.DefaultJSONHandler = jsonHandler
}

View File

@ -6,20 +6,15 @@ package xorm
import (
"errors"
"fmt"
"strconv"
"xorm.io/builder"
"xorm.io/xorm/caches"
"xorm.io/xorm/schemas"
)
var (
// ErrNeedDeletedCond delete needs less one condition error
ErrNeedDeletedCond = errors.New("Delete action needs at least one condition")
// ErrNotImplemented not implemented
ErrNotImplemented = errors.New("Not implemented")
)
// ErrNeedDeletedCond delete needs less one condition error
var ErrNeedDeletedCond = errors.New("Delete action needs at least one condition")
func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr string, args ...interface{}) error {
if table == nil ||
@ -28,7 +23,7 @@ func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr stri
}
for _, filter := range session.engine.dialect.Filters() {
sqlStr = filter.Do(sqlStr)
sqlStr = filter.Do(session.ctx, sqlStr)
}
newsql := session.statement.ConvertIDSQL(sqlStr)
@ -40,7 +35,13 @@ func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr stri
pkColumns := table.PKColumns()
ids, err := caches.GetCacheSql(cacher, tableName, newsql, args)
if err != nil {
resultsSlice, err := session.queryBytes(newsql, args...)
rows, err := session.queryRows(newsql, args...)
if err != nil {
return err
}
defer rows.Close()
resultsSlice, err := session.engine.ScanStringMaps(rows)
if err != nil {
return err
}
@ -53,9 +54,9 @@ func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr stri
if v, ok := data[col.Name]; !ok {
return errors.New("no id")
} else if col.SQLType.IsText() {
pk = append(pk, string(v))
pk = append(pk, v)
} else if col.SQLType.IsNumeric() {
id, err = strconv.ParseInt(string(v), 10, 64)
id, err = strconv.ParseInt(v, 10, 64)
if err != nil {
return err
}
@ -83,7 +84,18 @@ func (session *Session) cacheDelete(table *schemas.Table, tableName, sqlStr stri
}
// Delete records, bean's non-empty fields are conditions
// At least one condition must be set.
func (session *Session) Delete(beans ...interface{}) (int64, error) {
return session.delete(beans, true)
}
// Truncate records, bean's non-empty fields are conditions
// In contrast to Delete this method allows deletes without conditions.
func (session *Session) Truncate(beans ...interface{}) (int64, error) {
return session.delete(beans, false)
}
func (session *Session) delete(beans []interface{}, mustHaveConditions bool) (int64, error) {
if session.isAutoClose {
defer session.Close()
}
@ -93,10 +105,8 @@ func (session *Session) Delete(beans ...interface{}) (int64, error) {
}
var (
condSQL string
condArgs []interface{}
err error
bean interface{}
err error
bean interface{}
)
if len(beans) > 0 {
bean = beans[0]
@ -110,124 +120,50 @@ func (session *Session) Delete(beans ...interface{}) (int64, error) {
processor.BeforeDelete()
}
condSQL, condArgs, err = session.statement.GenConds(bean)
} else {
condSQL, condArgs, err = session.statement.GenCondSQL(session.statement.Conds())
}
if err != nil {
return 0, err
if err = session.statement.MergeConds(bean); err != nil {
return 0, err
}
}
pLimitN := session.statement.LimitN
if len(condSQL) == 0 && (pLimitN == nil || *pLimitN == 0) {
if mustHaveConditions && !session.statement.Conds().IsValid() && (pLimitN == nil || *pLimitN == 0) {
return 0, ErrNeedDeletedCond
}
var tableNameNoQuote = session.statement.TableName()
var tableName = session.engine.Quote(tableNameNoQuote)
var table = session.statement.RefTable
var deleteSQL string
if len(condSQL) > 0 {
deleteSQL = fmt.Sprintf("DELETE FROM %v WHERE %v", tableName, condSQL)
} else {
deleteSQL = fmt.Sprintf("DELETE FROM %v", tableName)
tableNameNoQuote := session.statement.TableName()
table := session.statement.RefTable
realSQLWriter := builder.NewWriter()
deleteSQLWriter := builder.NewWriter()
if err := session.statement.WriteDelete(realSQLWriter, deleteSQLWriter, session.engine.nowTime); err != nil {
return 0, err
}
var orderSQL string
if len(session.statement.OrderStr) > 0 {
orderSQL += fmt.Sprintf(" ORDER BY %s", session.statement.OrderStr)
}
if pLimitN != nil && *pLimitN > 0 {
limitNValue := *pLimitN
orderSQL += fmt.Sprintf(" LIMIT %d", limitNValue)
}
if len(orderSQL) > 0 {
switch session.engine.dialect.URI().DBType {
case schemas.POSTGRES:
inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL)
if len(condSQL) > 0 {
deleteSQL += " AND " + inSQL
} else {
deleteSQL += " WHERE " + inSQL
}
case schemas.SQLITE:
inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL)
if len(condSQL) > 0 {
deleteSQL += " AND " + inSQL
} else {
deleteSQL += " WHERE " + inSQL
}
// TODO: how to handle delete limit on mssql?
case schemas.MSSQL:
return 0, ErrNotImplemented
default:
deleteSQL += orderSQL
}
}
var realSQL string
argsForCache := make([]interface{}, 0, len(condArgs)*2)
if session.statement.GetUnscoped() || table == nil || table.DeletedColumn() == nil { // tag "deleted" is disabled
realSQL = deleteSQL
copy(argsForCache, condArgs)
argsForCache = append(condArgs, argsForCache...)
} else {
// !oinume! sqlStrForCache and argsForCache is needed to behave as executing "DELETE FROM ..." for caches.
copy(argsForCache, condArgs)
argsForCache = append(condArgs, argsForCache...)
deletedColumn := table.DeletedColumn()
realSQL = fmt.Sprintf("UPDATE %v SET %v = ? WHERE %v",
session.engine.Quote(session.statement.TableName()),
session.engine.Quote(deletedColumn.Name),
condSQL)
if len(orderSQL) > 0 {
switch session.engine.dialect.URI().DBType {
case schemas.POSTGRES:
inSQL := fmt.Sprintf("ctid IN (SELECT ctid FROM %s%s)", tableName, orderSQL)
if len(condSQL) > 0 {
realSQL += " AND " + inSQL
} else {
realSQL += " WHERE " + inSQL
}
case schemas.SQLITE:
inSQL := fmt.Sprintf("rowid IN (SELECT rowid FROM %s%s)", tableName, orderSQL)
if len(condSQL) > 0 {
realSQL += " AND " + inSQL
} else {
realSQL += " WHERE " + inSQL
}
// TODO: how to handle delete limit on mssql?
case schemas.MSSQL:
return 0, ErrNotImplemented
default:
realSQL += orderSQL
}
_, t, err := session.engine.nowTime(deletedColumn)
if err != nil {
return 0, err
}
// !oinume! Insert nowTime to the head of session.statement.Params
condArgs = append(condArgs, "")
paramsLen := len(condArgs)
copy(condArgs[1:paramsLen], condArgs[0:paramsLen-1])
val, t := session.engine.nowTime(deletedColumn)
condArgs[0] = val
var colName = deletedColumn.Name
colName := deletedColumn.Name
session.afterClosures = append(session.afterClosures, func(bean interface{}) {
col := table.GetColumn(colName)
setColumnTime(bean, col, t)
})
}
argsForCache := make([]interface{}, 0, len(deleteSQLWriter.Args())*2)
copy(argsForCache, deleteSQLWriter.Args())
argsForCache = append(deleteSQLWriter.Args(), argsForCache...)
if cacher := session.engine.GetCacher(tableNameNoQuote); cacher != nil && session.statement.UseCache {
session.cacheDelete(table, tableNameNoQuote, deleteSQL, argsForCache...)
_ = session.cacheDelete(table, tableNameNoQuote, deleteSQLWriter.String(), argsForCache...)
}
session.statement.RefTable = table
res, err := session.exec(realSQL, condArgs...)
res, err := session.exec(realSQLWriter.String(), realSQLWriter.Args()...)
if err != nil {
return 0, err
}

View File

@ -5,12 +5,14 @@
package xorm
import (
"database/sql"
"errors"
"reflect"
"strings"
"xorm.io/builder"
"xorm.io/xorm/caches"
"xorm.io/xorm/internal/convert"
"xorm.io/xorm/convert"
"xorm.io/xorm/internal/statements"
"xorm.io/xorm/internal/utils"
"xorm.io/xorm/schemas"
@ -57,12 +59,10 @@ func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...inte
if session.statement.SelectStr != "" {
session.statement.SelectStr = ""
}
if len(session.statement.ColumnMap) > 0 {
if len(session.statement.ColumnMap) > 0 && !session.statement.IsDistinct {
session.statement.ColumnMap = []string{}
}
if session.statement.OrderStr != "" {
session.statement.OrderStr = ""
}
session.statement.ResetOrderBy()
if session.statement.LimitN != nil {
session.statement.LimitN = nil
}
@ -71,7 +71,11 @@ func (session *Session) FindAndCount(rowsSlicePtr interface{}, condiBean ...inte
}
// session has stored the conditions so we use `unscoped` to avoid duplicated condition.
return session.Unscoped().Count(reflect.New(sliceElementType).Interface())
if sliceElementType.Kind() == reflect.Struct {
return session.Unscoped().Count(reflect.New(sliceElementType).Interface())
}
return session.Unscoped().Count()
}
func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{}) error {
@ -81,15 +85,15 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
}
sliceValue := reflect.Indirect(reflect.ValueOf(rowsSlicePtr))
var isSlice = sliceValue.Kind() == reflect.Slice
var isMap = sliceValue.Kind() == reflect.Map
isSlice := sliceValue.Kind() == reflect.Slice
isMap := sliceValue.Kind() == reflect.Map
if !isSlice && !isMap {
return errors.New("needs a pointer to a slice or a map")
}
sliceElementType := sliceValue.Type().Elem()
var tp = tpStruct
tp := tpStruct
if session.statement.RefTable == nil {
if sliceElementType.Kind() == reflect.Ptr {
if sliceElementType.Elem().Kind() == reflect.Struct {
@ -112,7 +116,7 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
var (
table = session.statement.RefTable
addedTableName = (len(session.statement.JoinStr) > 0)
addedTableName = session.statement.NeedTableName()
autoCond builder.Cond
)
if tp == tpStruct {
@ -152,7 +156,6 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
if err != ErrCacheFailed {
return err
}
err = nil // !nashtsai! reset err to nil for ErrCacheFailed
session.engine.logger.Warnf("Cache Find Failed")
}
}
@ -160,7 +163,75 @@ func (session *Session) find(rowsSlicePtr interface{}, condiBean ...interface{})
return session.noCacheFind(table, sliceValue, sqlStr, args...)
}
type QueryedField struct {
FieldName string
LowerFieldName string
ColumnType *sql.ColumnType
TempIndex int
ColumnSchema *schemas.Column
}
type ColumnsSchema struct {
Fields []*QueryedField
FieldNames []string
Types []*sql.ColumnType
}
func (columnsSchema *ColumnsSchema) ParseTableSchema(table *schemas.Table) {
for _, field := range columnsSchema.Fields {
field.ColumnSchema = table.GetColumnIdx(field.FieldName, field.TempIndex)
}
}
func ParseColumnsSchema(fieldNames []string, types []*sql.ColumnType, table *schemas.Table) *ColumnsSchema {
var columnsSchema ColumnsSchema
fields := make([]*QueryedField, 0, len(fieldNames))
for i, fieldName := range fieldNames {
field := &QueryedField{
FieldName: fieldName,
LowerFieldName: strings.ToLower(fieldName),
ColumnType: types[i],
}
fields = append(fields, field)
}
columnsSchema.Fields = fields
tempMap := make(map[string]int)
for _, field := range fields {
var idx int
var ok bool
if idx, ok = tempMap[field.LowerFieldName]; !ok {
idx = 0
} else {
idx++
}
tempMap[field.LowerFieldName] = idx
field.TempIndex = idx
}
if table != nil {
columnsSchema.ParseTableSchema(table)
}
return &columnsSchema
}
func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect.Value, sqlStr string, args ...interface{}) error {
elemType := containerValue.Type().Elem()
var isPointer bool
if elemType.Kind() == reflect.Ptr {
isPointer = true
elemType = elemType.Elem()
}
if elemType.Kind() == reflect.Ptr {
return errors.New("pointer to pointer is not supported")
}
rows, err := session.queryRows(sqlStr, args...)
if err != nil {
return err
@ -177,31 +248,8 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect
return err
}
var newElemFunc func(fields []string) reflect.Value
elemType := containerValue.Type().Elem()
var isPointer bool
if elemType.Kind() == reflect.Ptr {
isPointer = true
elemType = elemType.Elem()
}
if elemType.Kind() == reflect.Ptr {
return errors.New("pointer to pointer is not supported")
}
newElemFunc = func(fields []string) reflect.Value {
switch elemType.Kind() {
case reflect.Slice:
slice := reflect.MakeSlice(elemType, len(fields), len(fields))
x := reflect.New(slice.Type())
x.Elem().Set(slice)
return x
case reflect.Map:
mp := reflect.MakeMap(elemType)
x := reflect.New(mp.Type())
x.Elem().Set(mp)
return x
}
return reflect.New(elemType)
newElemFunc := func(fields []string) reflect.Value {
return utils.New(elemType, len(fields), len(fields))
}
var containerValueSetFunc func(*reflect.Value, schemas.PK) error
@ -226,10 +274,15 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect
containerValueSetFunc = func(newValue *reflect.Value, pk schemas.PK) error {
keyValue := reflect.New(keyType)
err := convertPKToValue(table, keyValue.Interface(), pk)
if err != nil {
return err
cols := table.PKColumns()
if len(cols) == 1 {
if err := convert.AssignValue(keyValue, pk[0]); err != nil {
return err
}
} else {
keyValue.Set(reflect.ValueOf(&pk))
}
if isPointer {
containerValue.SetMapIndex(keyValue.Elem(), newValue.Elem().Addr())
} else {
@ -240,13 +293,15 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect
}
if elemType.Kind() == reflect.Struct {
var newValue = newElemFunc(fields)
dataStruct := utils.ReflectValue(newValue.Interface())
tb, err := session.engine.tagParser.ParseWithCache(dataStruct)
newValue := newElemFunc(fields)
tb, err := session.engine.tagParser.ParseWithCache(newValue)
if err != nil {
return err
}
err = session.rows2Beans(rows, fields, types, tb, newElemFunc, containerValueSetFunc)
columnsSchema := ParseColumnsSchema(fields, types, tb)
err = session.rows2Beans(rows, columnsSchema, fields, types, tb, newElemFunc, containerValueSetFunc)
rows.Close()
if err != nil {
return err
@ -255,18 +310,17 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect
}
for rows.Next() {
var newValue = newElemFunc(fields)
newValue := newElemFunc(fields)
bean := newValue.Interface()
switch elemType.Kind() {
case reflect.Slice:
err = rows.ScanSlice(bean)
err = session.getSlice(rows, types, fields, bean)
case reflect.Map:
err = rows.ScanMap(bean)
err = session.getMap(rows, types, fields, bean)
default:
err = rows.Scan(bean)
}
if err != nil {
return err
}
@ -278,16 +332,6 @@ func (session *Session) noCacheFind(table *schemas.Table, containerValue reflect
return rows.Err()
}
func convertPKToValue(table *schemas.Table, dst interface{}, pk schemas.PK) error {
cols := table.PKColumns()
if len(cols) == 1 {
return convert.Assign(dst, pk[0], nil, nil)
}
dst = pk
return nil
}
func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr interface{}, args ...interface{}) (err error) {
if !session.canCache() ||
utils.IndexNoCase(sqlStr, "having") != -1 ||
@ -302,7 +346,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
}
for _, filter := range session.engine.dialect.Filters() {
sqlStr = filter.Do(sqlStr)
sqlStr = filter.Do(session.ctx, sqlStr)
}
newsql := session.statement.ConvertIDSQL(sqlStr)
@ -327,7 +371,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
session.engine.logger.Debugf("[cacheFind] ids length > 500, no cache")
return ErrCacheFailed
}
var res = make([]string, len(table.PrimaryKeys))
res := make([]string, len(table.PrimaryKeys))
err = rows.ScanSlice(&res)
if err != nil {
return err
@ -359,7 +403,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
ididxes := make(map[string]int)
var ides []schemas.PK
var temps = make([]interface{}, len(ids))
temps := make([]interface{}, len(ids))
for idx, id := range ids {
sid, err := id.ToString()
@ -474,7 +518,7 @@ func (session *Session) cacheFind(t reflect.Type, sqlStr string, rowsSlicePtr in
sliceValue.Set(reflect.Append(sliceValue, reflect.Indirect(reflect.ValueOf(bean))))
}
} else if sliceValue.Kind() == reflect.Map {
var key = ids[j]
key := ids[j]
keyType := sliceValue.Type().Key()
keyValue := reflect.New(keyType)
var ikey interface{}

Some files were not shown because too many files have changed in this diff Show More