From 6ffdff70950eacc74cd91a3a00d8d385018437de Mon Sep 17 00:00:00 2001 From: John Guo Date: Tue, 15 Feb 2022 23:43:47 +0800 Subject: [PATCH] fix issue #1537 --- contrib/drivers/mssql/mssql.go | 26 ++++++------- contrib/drivers/oracle/oracle.go | 26 ++++++------- contrib/drivers/pgsql/pgsql.go | 59 +++++++++++++++++++---------- contrib/drivers/pgsql/pgsql_test.go | 37 ++++++++++++++++++ contrib/drivers/sqlite/sqlite.go | 24 ++++++------ 5 files changed, 113 insertions(+), 59 deletions(-) create mode 100644 contrib/drivers/pgsql/pgsql_test.go diff --git a/contrib/drivers/mssql/mssql.go b/contrib/drivers/mssql/mssql.go index 6fdf45618..908aba1a8 100644 --- a/contrib/drivers/mssql/mssql.go +++ b/contrib/drivers/mssql/mssql.go @@ -29,8 +29,8 @@ import ( "github.com/gogf/gf/v2/text/gstr" ) -// DriverMssql is the driver for SQL server database. -type DriverMssql struct { +// Driver is the driver for SQL server database. +type Driver struct { *gdb.Core } @@ -47,19 +47,19 @@ func init() { // New create and returns a driver that implements gdb.Driver, which supports operations for Mssql. func New() gdb.Driver { - return &DriverMssql{} + return &Driver{} } // New creates and returns a database object for SQL server. // It implements the interface of gdb.Driver for extra database driver installation. -func (d *DriverMssql) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) { - return &DriverMssql{ +func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) { + return &Driver{ Core: core, }, nil } // Open creates and returns an underlying sql.DB object for mssql. -func (d *DriverMssql) Open(config *gdb.ConfigNode) (db *sql.DB, err error) { +func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) { var ( source string underlyingDriverName = "sqlserver" @@ -85,7 +85,7 @@ func (d *DriverMssql) Open(config *gdb.ConfigNode) (db *sql.DB, err error) { // FilteredLink retrieves and returns filtered `linkInfo` that can be using for // logging or tracing purpose. -func (d *DriverMssql) FilteredLink() string { +func (d *Driver) FilteredLink() string { linkInfo := d.GetConfig().Link if linkInfo == "" { return "" @@ -99,12 +99,12 @@ func (d *DriverMssql) FilteredLink() string { } // GetChars returns the security char for this type of database. -func (d *DriverMssql) GetChars() (charLeft string, charRight string) { +func (d *Driver) GetChars() (charLeft string, charRight string) { return "\"", "\"" } // DoFilter deals with the sql string before commits it to underlying sql driver. -func (d *DriverMssql) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { +func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { defer func() { newSql, newArgs, err = d.Core.DoFilter(ctx, link, newSql, newArgs) }() @@ -120,7 +120,7 @@ func (d *DriverMssql) DoFilter(ctx context.Context, link gdb.Link, sql string, a // parseSql does some replacement of the sql before commits it to underlying driver, // for support of microsoft sql server. -func (d *DriverMssql) parseSql(sql string) string { +func (d *Driver) parseSql(sql string) string { // SELECT * FROM USER WHERE ID=1 LIMIT 1 if m, _ := gregex.MatchString(`^SELECT(.+)LIMIT 1$`, sql); len(m) > 1 { return fmt.Sprintf(`SELECT TOP 1 %s`, m[1]) @@ -216,7 +216,7 @@ func (d *DriverMssql) parseSql(sql string) string { // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. -func (d *DriverMssql) Tables(ctx context.Context, schema ...string) (tables []string, err error) { +func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result gdb.Result link, err := d.SlaveLink(schema...) if err != nil { @@ -238,7 +238,7 @@ func (d *DriverMssql) Tables(ctx context.Context, schema ...string) (tables []st // TableFields retrieves and returns the fields' information of specified table of current schema. // // Also see DriverMysql.TableFields. -func (d *DriverMssql) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) { +func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { @@ -316,7 +316,7 @@ ORDER BY a.id,a.colorder`, } // DoInsert is not supported in mssql. -func (d *DriverMssql) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) { +func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) { switch option.InsertOption { case gdb.InsertOptionSave: return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by mssql driver`) diff --git a/contrib/drivers/oracle/oracle.go b/contrib/drivers/oracle/oracle.go index 581a8ef62..4ea25c96b 100644 --- a/contrib/drivers/oracle/oracle.go +++ b/contrib/drivers/oracle/oracle.go @@ -32,8 +32,8 @@ import ( "github.com/gogf/gf/v2/util/gconv" ) -// DriverOracle is the driver for oracle database. -type DriverOracle struct { +// Driver is the driver for oracle database. +type Driver struct { *gdb.Core } @@ -50,19 +50,19 @@ func init() { // New create and returns a driver that implements gdb.Driver, which supports operations for Oracle. func New() gdb.Driver { - return &DriverOracle{} + return &Driver{} } // New creates and returns a database object for oracle. // It implements the interface of gdb.Driver for extra database driver installation. -func (d *DriverOracle) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) { - return &DriverOracle{ +func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) { + return &Driver{ Core: core, }, nil } // Open creates and returns an underlying sql.DB object for oracle. -func (d *DriverOracle) Open(config *gdb.ConfigNode) (db *sql.DB, err error) { +func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) { var ( source string underlyingDriverName = "oci8" @@ -88,7 +88,7 @@ func (d *DriverOracle) Open(config *gdb.ConfigNode) (db *sql.DB, err error) { // FilteredLink retrieves and returns filtered `linkInfo` that can be using for // logging or tracing purpose. -func (d *DriverOracle) FilteredLink() string { +func (d *Driver) FilteredLink() string { linkInfo := d.GetConfig().Link if linkInfo == "" { return "" @@ -102,12 +102,12 @@ func (d *DriverOracle) FilteredLink() string { } // GetChars returns the security char for this type of database. -func (d *DriverOracle) GetChars() (charLeft string, charRight string) { +func (d *Driver) GetChars() (charLeft string, charRight string) { return "\"", "\"" } // DoFilter deals with the sql string before commits it to underlying sql driver. -func (d *DriverOracle) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { +func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { defer func() { newSql, newArgs, err = d.Core.DoFilter(ctx, link, newSql, newArgs) }() @@ -136,7 +136,7 @@ func (d *DriverOracle) DoFilter(ctx context.Context, link gdb.Link, sql string, // parseSql does some replacement of the sql before commits it to underlying driver, // for support of oracle server. -func (d *DriverOracle) parseSql(sql string) string { +func (d *Driver) parseSql(sql string) string { var ( patten = `^\s*(?i)(SELECT)|(LIMIT\s*(\d+)\s*,{0,1}\s*(\d*))` allMatch, _ = gregex.MatchAllString(patten, sql) @@ -192,7 +192,7 @@ func (d *DriverOracle) parseSql(sql string) string { // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. // Note that it ignores the parameter `schema` in oracle database, as it is not necessary. -func (d *DriverOracle) Tables(ctx context.Context, schema ...string) (tables []string, err error) { +func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result gdb.Result result, err = d.DoGetAll(ctx, nil, "SELECT TABLE_NAME FROM USER_TABLES ORDER BY TABLE_NAME") if err != nil { @@ -209,7 +209,7 @@ func (d *DriverOracle) Tables(ctx context.Context, schema ...string) (tables []s // TableFields retrieves and returns the fields' information of specified table of current schema. // // Also see DriverMysql.TableFields. -func (d *DriverOracle) TableFields( +func (d *Driver) TableFields( ctx context.Context, table string, schema ...string, ) (fields map[string]*gdb.TableField, err error) { charL, charR := d.GetChars() @@ -278,7 +278,7 @@ FROM USER_TAB_COLUMNS WHERE TABLE_NAME = '%s' ORDER BY COLUMN_ID`, // 1: replace: if there's unique/primary key in the data, it deletes it from table and inserts a new one; // 2: save: if there's unique/primary key in the data, it updates it or else inserts a new one; // 3: ignore: if there's unique/primary key in the data, it ignores the inserting; -func (d *DriverOracle) DoInsert( +func (d *Driver) DoInsert( ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption, ) (result sql.Result, err error) { switch option.InsertOption { diff --git a/contrib/drivers/pgsql/pgsql.go b/contrib/drivers/pgsql/pgsql.go index 396cdd1b5..4f87cdc01 100644 --- a/contrib/drivers/pgsql/pgsql.go +++ b/contrib/drivers/pgsql/pgsql.go @@ -28,8 +28,8 @@ import ( "github.com/gogf/gf/v2/text/gstr" ) -// DriverPgsql is the driver for postgresql database. -type DriverPgsql struct { +// Driver is the driver for postgresql database. +type Driver struct { *gdb.Core } @@ -46,19 +46,19 @@ func init() { // New create and returns a driver that implements gdb.Driver, which supports operations for PostgreSql. func New() gdb.Driver { - return &DriverPgsql{} + return &Driver{} } // New creates and returns a database object for postgresql. // It implements the interface of gdb.Driver for extra database driver installation. -func (d *DriverPgsql) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) { - return &DriverPgsql{ +func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) { + return &Driver{ Core: core, }, nil } // Open creates and returns an underlying sql.DB object for pgsql. -func (d *DriverPgsql) Open(config *gdb.ConfigNode) (db *sql.DB, err error) { +func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) { var ( source string underlyingDriverName = "postgres" @@ -87,7 +87,7 @@ func (d *DriverPgsql) Open(config *gdb.ConfigNode) (db *sql.DB, err error) { // FilteredLink retrieves and returns filtered `linkInfo` that can be using for // logging or tracing purpose. -func (d *DriverPgsql) FilteredLink() string { +func (d *Driver) FilteredLink() string { linkInfo := d.GetConfig().Link if linkInfo == "" { return "" @@ -101,21 +101,27 @@ func (d *DriverPgsql) FilteredLink() string { } // GetChars returns the security char for this type of database. -func (d *DriverPgsql) GetChars() (charLeft string, charRight string) { +func (d *Driver) GetChars() (charLeft string, charRight string) { return "\"", "\"" } // DoFilter deals with the sql string before commits it to underlying sql driver. -func (d *DriverPgsql) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { +func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { defer func() { newSql, newArgs, err = d.Core.DoFilter(ctx, link, newSql, newArgs) }() - var index int // Convert placeholder char '?' to string "$x". - sql, _ = gregex.ReplaceStringFunc("\\?", sql, func(s string) string { + sql, _ = gregex.ReplaceStringFunc(`\?`, sql, func(s string) string { index++ - return fmt.Sprintf("$%d", index) + return fmt.Sprintf(`$%d`, index) + }) + // Handle pgsql jsonb feature support, which contains place holder char '?'. + // Refer: + // https://github.com/gogf/gf/issues/1537 + // https://www.postgresql.org/docs/12/functions-json.html + sql, _ = gregex.ReplaceStringFuncMatch(`(::jsonb([^\w\d]*)\$\d)`, sql, func(match []string) string { + return fmt.Sprintf(`::jsonb%s?`, match[2]) }) newSql, _ = gregex.ReplaceString(` LIMIT (\d+),\s*(\d+)`, ` LIMIT $2 OFFSET $1`, sql) return newSql, args, nil @@ -123,7 +129,7 @@ func (d *DriverPgsql) DoFilter(ctx context.Context, link gdb.Link, sql string, a // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. -func (d *DriverPgsql) Tables(ctx context.Context, schema ...string) (tables []string, err error) { +func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result gdb.Result link, err := d.SlaveLink(schema...) if err != nil { @@ -131,7 +137,10 @@ func (d *DriverPgsql) Tables(ctx context.Context, schema ...string) (tables []st } query := "SELECT TABLENAME FROM PG_TABLES WHERE SCHEMANAME = 'public' ORDER BY TABLENAME" if len(schema) > 0 && schema[0] != "" { - query = fmt.Sprintf("SELECT TABLENAME FROM PG_TABLES WHERE SCHEMANAME = '%s' ORDER BY TABLENAME", schema[0]) + query = fmt.Sprintf( + "SELECT TABLENAME FROM PG_TABLES WHERE SCHEMANAME = '%s' ORDER BY TABLENAME", + schema[0], + ) } result, err = d.DoGetAll(ctx, link, query) if err != nil { @@ -148,11 +157,14 @@ func (d *DriverPgsql) Tables(ctx context.Context, schema ...string) (tables []st // TableFields retrieves and returns the fields' information of specified table of current schema. // // Also see DriverMysql.TableFields. -func (d *DriverPgsql) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) { +func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { - return nil, gerror.NewCode(gcode.CodeInvalidParameter, "function TableFields supports only single table operations") + return nil, gerror.NewCode( + gcode.CodeInvalidParameter, + "function TableFields supports only single table operations", + ) } table, _ = gregex.ReplaceString("\"", "", table) useSchema := d.GetSchema() @@ -212,13 +224,19 @@ ORDER BY a.attnum`, } // DoInsert is not supported in pgsql. -func (d *DriverPgsql) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) { +func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) { switch option.InsertOption { case gdb.InsertOptionSave: - return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by pgsql driver`) + return nil, gerror.NewCode( + gcode.CodeNotSupported, + `Save operation is not supported by pgsql driver`, + ) case gdb.InsertOptionReplace: - return nil, gerror.NewCode(gcode.CodeNotSupported, `Replace operation is not supported by pgsql driver`) + return nil, gerror.NewCode( + gcode.CodeNotSupported, + `Replace operation is not supported by pgsql driver`, + ) default: return d.Core.DoInsert(ctx, link, table, list, option) @@ -226,7 +244,7 @@ func (d *DriverPgsql) DoInsert(ctx context.Context, link gdb.Link, table string, } // ConvertDataForRecord converting for any data that will be inserted into table/collection as a record. -func (d *DriverPgsql) ConvertDataForRecord(ctx context.Context, value interface{}) map[string]interface{} { +func (d *Driver) ConvertDataForRecord(ctx context.Context, value interface{}) map[string]interface{} { data := gdb.DataToMapDeep(value) var err error for k, v := range data { @@ -239,6 +257,5 @@ func (d *DriverPgsql) ConvertDataForRecord(ctx context.Context, value interface{ data[k] = d.Core.ConvertDataForRecordValue(ctx, v) } } - return data } diff --git a/contrib/drivers/pgsql/pgsql_test.go b/contrib/drivers/pgsql/pgsql_test.go new file mode 100644 index 000000000..481d666cf --- /dev/null +++ b/contrib/drivers/pgsql/pgsql_test.go @@ -0,0 +1,37 @@ +// Copyright GoFrame Author(https://goframe.org). All Rights Reserved. +// +// This Source Code Form is subject to the terms of the MIT License. +// If a copy of the MIT was not distributed with this file, +// You can obtain one at https://github.com/gogf/gf. + +package pgsql_test + +import ( + "testing" + + "github.com/gogf/gf/contrib/drivers/pgsql/v2" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/os/gctx" + "github.com/gogf/gf/v2/test/gtest" +) + +func Test_Driver_DoFilter(t *testing.T) { + var ( + ctx = gctx.New() + driver = pgsql.Driver{} + ) + gtest.C(t, func(t *gtest.T) { + var data = g.Map{ + `select * from user where (role)::jsonb ?| 'admin'`: `select * from user where (role)::jsonb ?| 'admin'`, + `select * from user where (role)::jsonb ?| '?'`: `select * from user where (role)::jsonb ?| '$2'`, + `select * from user where (role)::jsonb &? '?'`: `select * from user where (role)::jsonb &? '$2'`, + `select * from user where (role)::jsonb ? '?'`: `select * from user where (role)::jsonb ? '$2'`, + `select * from user where '?'`: `select * from user where '$1'`, + } + for k, v := range data { + newSql, _, err := driver.DoFilter(ctx, nil, k, nil) + t.AssertNil(err) + t.Assert(newSql, v) + } + }) +} diff --git a/contrib/drivers/sqlite/sqlite.go b/contrib/drivers/sqlite/sqlite.go index 08cbdc339..bca87fb33 100644 --- a/contrib/drivers/sqlite/sqlite.go +++ b/contrib/drivers/sqlite/sqlite.go @@ -27,8 +27,8 @@ import ( "github.com/gogf/gf/v2/text/gstr" ) -// DriverSqlite is the driver for sqlite database. -type DriverSqlite struct { +// Driver is the driver for sqlite database. +type Driver struct { *gdb.Core } @@ -45,19 +45,19 @@ func init() { // New create and returns a driver that implements gdb.Driver, which supports operations for SQLite. func New() gdb.Driver { - return &DriverSqlite{} + return &Driver{} } // New creates and returns a database object for sqlite. // It implements the interface of gdb.Driver for extra database driver installation. -func (d *DriverSqlite) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) { - return &DriverSqlite{ +func (d *Driver) New(core *gdb.Core, node *gdb.ConfigNode) (gdb.DB, error) { + return &Driver{ Core: core, }, nil } // Open creates and returns a underlying sql.DB object for sqlite. -func (d *DriverSqlite) Open(config *gdb.ConfigNode) (db *sql.DB, err error) { +func (d *Driver) Open(config *gdb.ConfigNode) (db *sql.DB, err error) { var ( source string underlyingDriverName = "sqlite3" @@ -83,23 +83,23 @@ func (d *DriverSqlite) Open(config *gdb.ConfigNode) (db *sql.DB, err error) { // FilteredLink retrieves and returns filtered `linkInfo` that can be using for // logging or tracing purpose. -func (d *DriverSqlite) FilteredLink() string { +func (d *Driver) FilteredLink() string { return d.GetConfig().Link } // GetChars returns the security char for this type of database. -func (d *DriverSqlite) GetChars() (charLeft string, charRight string) { +func (d *Driver) GetChars() (charLeft string, charRight string) { return "`", "`" } // DoFilter deals with the sql string before commits it to underlying sql driver. -func (d *DriverSqlite) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { +func (d *Driver) DoFilter(ctx context.Context, link gdb.Link, sql string, args []interface{}) (newSql string, newArgs []interface{}, err error) { return d.Core.DoFilter(ctx, link, sql, args) } // Tables retrieves and returns the tables of current schema. // It's mainly used in cli tool chain for automatically generating the models. -func (d *DriverSqlite) Tables(ctx context.Context, schema ...string) (tables []string, err error) { +func (d *Driver) Tables(ctx context.Context, schema ...string) (tables []string, err error) { var result gdb.Result link, err := d.SlaveLink(schema...) if err != nil { @@ -121,7 +121,7 @@ func (d *DriverSqlite) Tables(ctx context.Context, schema ...string) (tables []s // TableFields retrieves and returns the fields' information of specified table of current schema. // // Also see DriverMysql.TableFields. -func (d *DriverSqlite) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) { +func (d *Driver) TableFields(ctx context.Context, table string, schema ...string) (fields map[string]*gdb.TableField, err error) { charL, charR := d.GetChars() table = gstr.Trim(table, charL+charR) if gstr.Contains(table, " ") { @@ -163,7 +163,7 @@ func (d *DriverSqlite) TableFields(ctx context.Context, table string, schema ... } // DoInsert is not supported in sqlite. -func (d *DriverSqlite) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) { +func (d *Driver) DoInsert(ctx context.Context, link gdb.Link, table string, list gdb.List, option gdb.DoInsertOption) (result sql.Result, err error) { switch option.InsertOption { case gdb.InsertOptionSave: return nil, gerror.NewCode(gcode.CodeNotSupported, `Save operation is not supported by sqlite driver`)