diff --git a/README.md b/README.md index bf4a210e..22c7e355 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ In order to start using goqu with your database you need to load an adapter. We 1. Postgres - `import "github.com/doug-martin/goqu/adapters/postgres"` 2. MySQL - `import "github.com/doug-martin/goqu/adapters/mysql"` +3. SQLite3 - `import "github.com/doug-martin/goqu/adapters/sqlite3"` Adapters in goqu work the same way as a driver with the database in that they register themselves with goqu once loaded. @@ -170,7 +171,7 @@ Please see the exmaples for [`I()`](https://godoc.org/github.com/doug-martin/goq ```go goqu.L(`"col"::TEXT = ""other_col"::text`) ``` -You can also use placeholders in your literal. When using the LiteralExpressions placeholders are normalized to the ? character and will be transformed to the correct placeholder for your adapter (e.g. `?` mysql, `$1` postgres) +You can also use placeholders in your literal. When using the LiteralExpressions placeholders are normalized to the ? character and will be transformed to the correct placeholder for your adapter (e.g. `?` mysql, `$1` postgres, `?` sqlite3) ```go goqu.L("col IN (?, ?, ?)", "a", "b", "c") ``` @@ -590,7 +591,7 @@ func init() { } ``` -If you are looking to write your own adapter take a look at the postgres or mysql adapter located at https://github.com/doug-martin/goqu/tree/master/adapters. +If you are looking to write your own adapter take a look at the postgres/mysql or sqlite3 adapter located at https://github.com/doug-martin/goqu/tree/master/adapters. ## Contributions diff --git a/adapters/sqlite3/dataset_adapter_test.go b/adapters/sqlite3/dataset_adapter_test.go new file mode 100644 index 00000000..59be9c5a --- /dev/null +++ b/adapters/sqlite3/dataset_adapter_test.go @@ -0,0 +1,182 @@ +package sqlite3 + +import ( + "github.com/doug-martin/goqu" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + "regexp" + "testing" +) + +type datasetAdapterTest struct { + suite.Suite +} + +func (me *datasetAdapterTest) TestPlaceholderSql() { + t := me.T() + buf := goqu.NewSqlBuilder(true) + dsAdapter := newDatasetAdapter(goqu.From("test")) + dsAdapter.PlaceHolderSql(buf, 1) + dsAdapter.PlaceHolderSql(buf, 2) + dsAdapter.PlaceHolderSql(buf, 3) + dsAdapter.PlaceHolderSql(buf, 4) + sql, args := buf.ToSql() + assert.Equal(t, args, []interface{}{1, 2, 3, 4}) + assert.Equal(t, sql, "????") +} + +func (me *datasetAdapterTest) GetDs(table string) *goqu.Dataset { + ret := goqu.From(table) + adapter := newDatasetAdapter(ret) + ret.SetAdapter(adapter) + return ret +} + +func (me *datasetAdapterTest) TestSupportsReturn() { + t := me.T() + dsAdapter := me.GetDs("test").Adapter() + assert.False(t, dsAdapter.SupportsReturn()) +} + +func (me *datasetAdapterTest) TestSupportsLimitOnDelete() { + t := me.T() + dsAdapter := me.GetDs("test").Adapter() + assert.True(t, dsAdapter.SupportsLimitOnDelete()) +} + +func (me *datasetAdapterTest) TestSupportsLimitOnUpdate() { + t := me.T() + dsAdapter := me.GetDs("test").Adapter() + assert.True(t, dsAdapter.SupportsLimitOnDelete()) +} + +func (me *datasetAdapterTest) TestSupportsOrderByOnDelete() { + t := me.T() + dsAdapter := me.GetDs("test").Adapter() + assert.True(t, dsAdapter.SupportsLimitOnDelete()) +} + +func (me *datasetAdapterTest) TestSupportsOrderByOnUpdate() { + t := me.T() + dsAdapter := me.GetDs("test").Adapter() + assert.True(t, dsAdapter.SupportsLimitOnDelete()) +} + +func (me *datasetAdapterTest) TestIdentifiers() { + t := me.T() + ds := me.GetDs("test") + sql, err := ds.Select("a", goqu.I("a.b.c"), goqu.I("c.d"), goqu.I("test").As("test")).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT `a`, `a`.`b`.`c`, `c`.`d`, `test` AS `test` FROM `test`") +} + +func (me *datasetAdapterTest) TestLiteralString() { + t := me.T() + ds := me.GetDs("test") + sql, err := ds.Where(goqu.I("a").Eq("test")).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test')") + + sql, err = ds.Where(goqu.I("a").Eq("test'test")).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\'test')") + + sql, err = ds.Where(goqu.I("a").Eq(`test"test`)).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\"test')") + + sql, err = ds.Where(goqu.I("a").Eq(`test\test`)).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\\\test')") + + sql, err = ds.Where(goqu.I("a").Eq("test\ntest")).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\ntest')") + + sql, err = ds.Where(goqu.I("a").Eq("test\rtest")).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\rtest')") + + sql, err = ds.Where(goqu.I("a").Eq("test\x00test")).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x00test')") + + sql, err = ds.Where(goqu.I("a").Eq("test\x1atest")).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` = 'test\\x1atest')") +} + +func (me *datasetAdapterTest) TestBooleanOperations() { + t := me.T() + ds := me.GetDs("test") + sql, err := ds.Where(goqu.I("a").Eq(true)).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS TRUE)") + sql, err = ds.Where(goqu.I("a").Eq(false)).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS FALSE)") + sql, err = ds.Where(goqu.I("a").Is(true)).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS TRUE)") + sql, err = ds.Where(goqu.I("a").Is(false)).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS FALSE)") + sql, err = ds.Where(goqu.I("a").IsTrue()).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS TRUE)") + sql, err = ds.Where(goqu.I("a").IsFalse()).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS FALSE)") + + sql, err = ds.Where(goqu.I("a").Neq(true)).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT TRUE)") + sql, err = ds.Where(goqu.I("a").Neq(false)).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT FALSE)") + sql, err = ds.Where(goqu.I("a").IsNot(true)).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT TRUE)") + sql, err = ds.Where(goqu.I("a").IsNot(false)).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT FALSE)") + sql, err = ds.Where(goqu.I("a").IsNotTrue()).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT TRUE)") + sql, err = ds.Where(goqu.I("a").IsNotFalse()).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` IS NOT FALSE)") + + sql, err = ds.Where(goqu.I("a").Like("a%")).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` LIKE BINARY 'a%')") + + sql, err = ds.Where(goqu.I("a").NotLike("a%")).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT LIKE BINARY 'a%')") + + sql, err = ds.Where(goqu.I("a").ILike("a%")).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` LIKE 'a%')") + sql, err = ds.Where(goqu.I("a").NotILike("a%")).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT LIKE 'a%')") + + sql, err = ds.Where(goqu.I("a").Like(regexp.MustCompile("(a|b)"))).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` REGEXP BINARY '(a|b)')") + sql, err = ds.Where(goqu.I("a").NotLike(regexp.MustCompile("(a|b)"))).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT REGEXP BINARY '(a|b)')") + sql, err = ds.Where(goqu.I("a").ILike(regexp.MustCompile("(a|b)"))).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` REGEXP '(a|b)')") + sql, err = ds.Where(goqu.I("a").NotILike(regexp.MustCompile("(a|b)"))).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `test` WHERE (`a` NOT REGEXP '(a|b)')") + +} + +func TestDatasetAdapterSuite(t *testing.T) { + suite.Run(t, new(datasetAdapterTest)) +} diff --git a/adapters/sqlite3/sqlite3.go b/adapters/sqlite3/sqlite3.go new file mode 100644 index 00000000..f3d130d8 --- /dev/null +++ b/adapters/sqlite3/sqlite3.go @@ -0,0 +1,104 @@ +package sqlite3 + +import ( + "github.com/doug-martin/goqu" +) + +var ( + placeholder_rune = '?' + quote_rune = '`' + singlq_quote = '\'' + default_values_frag = []byte("") + sqlite3_true = []byte("1") + sqlite3_false = []byte("0") + time_format = "2006-01-02 15:04:05" + operator_lookup = map[goqu.BooleanOperation][]byte{ + goqu.EQ_OP: []byte("="), + goqu.NEQ_OP: []byte("!="), + goqu.GT_OP: []byte(">"), + goqu.GTE_OP: []byte(">="), + goqu.LT_OP: []byte("<"), + goqu.LTE_OP: []byte("<="), + goqu.IN_OP: []byte("IN"), + goqu.NOT_IN_OP: []byte("NOT IN"), + goqu.IS_OP: []byte("IS"), + goqu.IS_NOT_OP: []byte("IS NOT"), + goqu.LIKE_OP: []byte("LIKE BINARY"), + goqu.NOT_LIKE_OP: []byte("NOT LIKE BINARY"), + goqu.I_LIKE_OP: []byte("LIKE"), + goqu.NOT_I_LIKE_OP: []byte("NOT LIKE"), + goqu.REGEXP_LIKE_OP: []byte("REGEXP BINARY"), + goqu.REGEXP_NOT_LIKE_OP: []byte("NOT REGEXP BINARY"), + goqu.REGEXP_I_LIKE_OP: []byte("REGEXP"), + goqu.REGEXP_NOT_I_LIKE_OP: []byte("NOT REGEXP"), + } +) + +type DatasetAdapter struct { + *goqu.DefaultAdapter +} + +func (me *DatasetAdapter) SupportsReturn() bool { + return false +} + +func (me *DatasetAdapter) SupportsLimitOnDelete() bool { + return true +} + +func (me *DatasetAdapter) SupportsLimitOnUpdate() bool { + return true +} + +func (me *DatasetAdapter) SupportsOrderByOnDelete() bool { + return true +} + +func (me *DatasetAdapter) SupportsOrderByOnUpdate() bool { + return true +} + +func (me *DatasetAdapter) LiteralString(buf *goqu.SqlBuilder, s string) error { + if buf.IsPrepared { + return me.PlaceHolderSql(buf, s) + } + buf.WriteRune(singlq_quote) + for _, char := range s { + if char == '\'' { // single quote: ' -> \' + buf.WriteString("\\'") + } else if char == '"' { // double quote: " -> \" + buf.WriteString("\\\"") + } else if char == '\\' { // slash: \ -> "\\" + buf.WriteString("\\\\") + } else if char == '\n' { // control: newline: \n -> "\n" + buf.WriteString("\\n") + } else if char == '\r' { // control: return: \r -> "\r" + buf.WriteString("\\r") + } else if char == 0 { // control: NUL: 0 -> "\x00" + buf.WriteString("\\x00") + } else if char == 0x1a { // control: \x1a -> "\x1a" + buf.WriteString("\\x1a") + } else { + buf.WriteRune(char) + } + } + buf.WriteRune(singlq_quote) + return nil +} + +func newDatasetAdapter(ds *goqu.Dataset) goqu.Adapter { + def := goqu.NewDefaultAdapter(ds).(*goqu.DefaultAdapter) + def.PlaceHolderRune = placeholder_rune + def.IncludePlaceholderNum = false + def.QuoteRune = quote_rune + def.DefaultValuesFragment = default_values_frag + def.True = sqlite3_true + def.False = sqlite3_false + def.TimeFormat = time_format + def.BooleanOperatorLookup = operator_lookup + return &DatasetAdapter{def} +} + +func init() { + goqu.RegisterAdapter("sqlite3", newDatasetAdapter) +} diff --git a/adapters/sqlite3/sqlite3_test.go b/adapters/sqlite3/sqlite3_test.go new file mode 100644 index 00000000..28d4e593 --- /dev/null +++ b/adapters/sqlite3/sqlite3_test.go @@ -0,0 +1,316 @@ +package sqlite3 + +import ( + "database/sql" + "fmt" + "github.com/doug-martin/goqu" + _ "github.com/mattn/go-sqlite3" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/suite" + "testing" + "time" +) + +const ( + drop_table = "DROP TABLE IF EXISTS `entry`;" + create_table = "CREATE TABLE `entry` (" + + "`id` INT NOT NULL AUTO_INCREMENT ," + + "`int` INT NOT NULL ," + + "`float` FLOAT NOT NULL ," + + "`string` VARCHAR(255) NOT NULL ," + + "`time` DATETIME NOT NULL ," + + "`bool` TINYINT NOT NULL ," + + "`bytes` BLOB NOT NULL ," + + "PRIMARY KEY (`id`) );" + insert_default_reords = "INSERT INTO `entry` (`int`, `float`, `string`, `time`, `bool`, `bytes`) VALUES" + + "(0, 0.000000, '0.000000', '2015-02-22 18:19:55', TRUE, '0.000000')," + + "(1, 0.100000, '0.100000', '2015-02-22 19:19:55', FALSE, '0.100000')," + + "(2, 0.200000, '0.200000', '2015-02-22 20:19:55', TRUE, '0.200000')," + + "(3, 0.300000, '0.300000', '2015-02-22 21:19:55', FALSE, '0.300000')," + + "(4, 0.400000, '0.400000', '2015-02-22 22:19:55', TRUE, '0.400000')," + + "(5, 0.500000, '0.500000', '2015-02-22 23:19:55', FALSE, '0.500000')," + + "(6, 0.600000, '0.600000', '2015-02-23 00:19:55', TRUE, '0.600000')," + + "(7, 0.700000, '0.700000', '2015-02-23 01:19:55', FALSE, '0.700000')," + + "(8, 0.800000, '0.800000', '2015-02-23 02:19:55', TRUE, '0.800000')," + + "(9, 0.900000, '0.900000', '2015-02-23 03:19:55', FALSE, '0.900000');" +) + +var db_uri = ":memory:" + +type ( + logger struct{} + sqlite3Test struct { + suite.Suite + db *goqu.Database + } + entry struct { + Id uint32 `db:"id" goqu:"skipinsert,skipupdate"` + Int int `db:"int"` + Float float64 `db:"float"` + String string `db:"string"` + Time time.Time `db:"time"` + Bool bool `db:"bool"` + Bytes []byte `db:"bytes"` + } +) + +func (me logger) Printf(sql string, args ...interface{}) { + fmt.Printf("\n"+sql, args) +} + +func (me *sqlite3Test) SetupSuite() { + fmt.Println(db_uri) + db, err := sql.Open("sqlite3", db_uri) + if err != nil { + panic(err.Error()) + } + me.db = goqu.New("sqlite3", db) + // me.db.Logger(logger{}) +} + +func (me *sqlite3Test) SetupTest() { + if _, err := me.db.Exec(drop_table); err != nil { + panic(err) + } + if _, err := me.db.Exec(create_table); err != nil { + panic(err) + } + if _, err := me.db.Exec(insert_default_reords); err != nil { + panic(err) + } +} + +func (me *sqlite3Test) TestSelectSql() { + t := me.T() + ds := me.db.From("entry") + sql, err := ds.Select("id", "float", "string", "time", "bool").Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT `id`, `float`, `string`, `time`, `bool` FROM `entry`") + + sql, err = ds.Where(goqu.I("int").Eq(10)).Sql() + assert.NoError(t, err) + assert.Equal(t, sql, "SELECT * FROM `entry` WHERE (`int` = 10)") + + sql, args, err := ds.Where(goqu.L("? = ?", goqu.I("int"), 10)).ToSql(true) + assert.NoError(t, err) + assert.Equal(t, args, []interface{}{10}) + assert.Equal(t, sql, "SELECT * FROM `entry` WHERE `int` = ?") +} + +func (me *sqlite3Test) TestQuery() { + t := me.T() + var entries []entry + ds := me.db.From("entry") + assert.NoError(t, ds.Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.Len(t, entries, 10) + floatVal := float64(0) + baseDate, err := time.Parse(time_format, "2015-02-22 18:19:55") + assert.NoError(t, err) + for i, entry := range entries { + f := fmt.Sprintf("%f", floatVal) + assert.Equal(t, entry.Id, uint32(i+1)) + assert.Equal(t, entry.Int, i) + assert.Equal(t, fmt.Sprintf("%f", entry.Float), f) + assert.Equal(t, entry.String, f) + assert.Equal(t, entry.Bytes, []byte(f)) + assert.Equal(t, entry.Bool, i%2 == 0) + assert.Equal(t, entry.Time, baseDate.Add(time.Duration(i)*time.Hour)) + floatVal += float64(0.1) + } + entries = entries[0:0] + assert.NoError(t, ds.Where(goqu.I("bool").IsTrue()).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.Len(t, entries, 5) + assert.NoError(t, err) + for _, entry := range entries { + assert.True(t, entry.Bool) + } + + entries = entries[0:0] + assert.NoError(t, ds.Where(goqu.I("int").Gt(4)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.Len(t, entries, 5) + assert.NoError(t, err) + for _, entry := range entries { + assert.True(t, entry.Int > 4) + } + + entries = entries[0:0] + assert.NoError(t, ds.Where(goqu.I("int").Gte(5)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.Len(t, entries, 5) + assert.NoError(t, err) + for _, entry := range entries { + assert.True(t, entry.Int >= 5) + } + + entries = entries[0:0] + assert.NoError(t, ds.Where(goqu.I("int").Lt(5)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.Len(t, entries, 5) + assert.NoError(t, err) + for _, entry := range entries { + assert.True(t, entry.Int < 5) + } + + entries = entries[0:0] + assert.NoError(t, ds.Where(goqu.I("int").Lte(4)).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.Len(t, entries, 5) + assert.NoError(t, err) + for _, entry := range entries { + assert.True(t, entry.Int <= 4) + } + + entries = entries[0:0] + assert.NoError(t, ds.Where(goqu.I("string").Eq("0.100000")).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.Len(t, entries, 1) + assert.NoError(t, err) + for _, entry := range entries { + assert.Equal(t, entry.String, "0.100000") + } + + entries = entries[0:0] + assert.NoError(t, ds.Where(goqu.I("string").Like("0.1%")).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.Len(t, entries, 1) + assert.NoError(t, err) + for _, entry := range entries { + assert.Equal(t, entry.String, "0.100000") + } + + entries = entries[0:0] + assert.NoError(t, ds.Where(goqu.I("string").NotLike("0.1%")).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.Len(t, entries, 9) + assert.NoError(t, err) + for _, entry := range entries { + assert.NotEqual(t, entry.String, "0.100000") + } + + entries = entries[0:0] + assert.NoError(t, ds.Where(goqu.I("string").IsNull()).Order(goqu.I("id").Asc()).ScanStructs(&entries)) + assert.Len(t, entries, 0) +} + +func (me *sqlite3Test) TestCount() { + t := me.T() + ds := me.db.From("entry") + count, err := ds.Count() + assert.NoError(t, err) + assert.Equal(t, count, 10) + count, err = ds.Where(goqu.I("int").Gt(4)).Count() + assert.NoError(t, err) + assert.Equal(t, count, 5) + count, err = ds.Where(goqu.I("int").Gte(4)).Count() + assert.NoError(t, err) + assert.Equal(t, count, 6) + count, err = ds.Where(goqu.I("string").Like("0.1%")).Count() + assert.NoError(t, err) + assert.Equal(t, count, 1) + count, err = ds.Where(goqu.I("string").IsNull()).Count() + assert.NoError(t, err) + assert.Equal(t, count, 0) +} + +func (me *sqlite3Test) TestInsert() { + t := me.T() + ds := me.db.From("entry") + now := time.Now() + e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")} + _, err := ds.Insert(e).Exec() + assert.NoError(t, err) + + var insertedEntry entry + found, err := ds.Where(goqu.I("int").Eq(10)).ScanStruct(&insertedEntry) + assert.NoError(t, err) + assert.True(t, found) + assert.True(t, insertedEntry.Id > 0) + + entries := []entry{ + {Int: 11, Float: 1.100000, String: "1.100000", Time: now, Bool: false, Bytes: []byte("1.100000")}, + {Int: 12, Float: 1.200000, String: "1.200000", Time: now, Bool: true, Bytes: []byte("1.200000")}, + {Int: 13, Float: 1.300000, String: "1.300000", Time: now, Bool: false, Bytes: []byte("1.300000")}, + {Int: 14, Float: 1.400000, String: "1.400000", Time: now, Bool: true, Bytes: []byte("1.400000")}, + } + _, err = ds.Insert(entries).Exec() + assert.NoError(t, err) + + var newEntries []entry + assert.NoError(t, ds.Where(goqu.I("int").In([]uint32{11, 12, 13, 14})).ScanStructs(&newEntries)) + assert.Len(t, newEntries, 4) + + _, err = ds.Insert( + entry{Int: 15, Float: 1.500000, String: "1.500000", Time: now, Bool: false, Bytes: []byte("1.500000")}, + entry{Int: 16, Float: 1.600000, String: "1.600000", Time: now, Bool: true, Bytes: []byte("1.600000")}, + entry{Int: 17, Float: 1.700000, String: "1.700000", Time: now, Bool: false, Bytes: []byte("1.700000")}, + entry{Int: 18, Float: 1.800000, String: "1.800000", Time: now, Bool: true, Bytes: []byte("1.800000")}, + ).Exec() + assert.NoError(t, err) + + newEntries = newEntries[0:0] + assert.NoError(t, ds.Where(goqu.I("int").In([]uint32{15, 16, 17, 18})).ScanStructs(&newEntries)) + assert.Len(t, newEntries, 4) +} + +func (me *sqlite3Test) TestInsertReturning() { + t := me.T() + ds := me.db.From("entry") + now := time.Now() + e := entry{Int: 10, Float: 1.000000, String: "1.000000", Time: now, Bool: true, Bytes: []byte("1.000000")} + _, err := ds.Returning(goqu.Star()).Insert(e).ScanStruct(&e) + assert.Error(t, err) + +} + +func (me *sqlite3Test) TestUpdate() { + t := me.T() + ds := me.db.From("entry") + var e entry + found, err := ds.Where(goqu.I("int").Eq(9)).Select("id").ScanStruct(&e) + assert.NoError(t, err) + assert.True(t, found) + e.Int = 11 + _, err = ds.Where(goqu.I("id").Eq(e.Id)).Update(e).Exec() + assert.NoError(t, err) + + count, err := ds.Where(goqu.I("int").Eq(11)).Count() + assert.NoError(t, err) + assert.Equal(t, count, 1) +} + +func (me *sqlite3Test) TestUpdateReturning() { + t := me.T() + ds := me.db.From("entry") + var id uint32 + _, err := ds.Where(goqu.I("int").Eq(11)).Returning("id").Update(map[string]interface{}{"int": 9}).ScanVal(&id) + assert.Error(t, err) + assert.Equal(t, err.Error(), "goqu: Adapter does not support RETURNING clause") +} + +func (me *sqlite3Test) TestDelete() { + t := me.T() + ds := me.db.From("entry") + var e entry + found, err := ds.Where(goqu.I("int").Eq(9)).Select("id").ScanStruct(&e) + assert.NoError(t, err) + assert.True(t, found) + _, err = ds.Where(goqu.I("id").Eq(e.Id)).Delete().Exec() + assert.NoError(t, err) + + count, err := ds.Count() + assert.NoError(t, err) + assert.Equal(t, count, 9) + + var id uint32 + found, err = ds.Where(goqu.I("id").Eq(e.Id)).ScanVal(&id) + assert.NoError(t, err) + assert.False(t, found) + + e = entry{} + found, err = ds.Where(goqu.I("int").Eq(8)).Select("id").ScanStruct(&e) + assert.NoError(t, err) + assert.True(t, found) + assert.NotEqual(t, e.Id, 0) + + id = 0 + _, err = ds.Where(goqu.I("id").Eq(e.Id)).Returning("id").Delete().ScanVal(&id) + assert.Equal(t, err.Error(), "goqu: Adapter does not support RETURNING clause") +} + +func Testsqlite3Suite(t *testing.T) { + suite.Run(t, new(sqlite3Test)) +} diff --git a/default_adapter.go b/default_adapter.go index fce71fc3..db531a99 100644 --- a/default_adapter.go +++ b/default_adapter.go @@ -110,7 +110,7 @@ type ( CascadeFragment []byte //The RESTRICT fragment to use when generating sql. (DEFAULT=[]byte(" RESTRICT")) RestrictFragment []byte - //The SQL fragment to use when generating insert sql and using DEFAULT VALUES (e.g. postgres="DEFAULT VALUES", mysql= ""). (DEFAULT=[]byte(" DEFAULT VALUES")) + //The SQL fragment to use when generating insert sql and using DEFAULT VALUES (e.g. postgres="DEFAULT VALUES", mysql="", sqlite3=""). (DEFAULT=[]byte(" DEFAULT VALUES")) DefaultValuesFragment []byte //The SQL fragment to use when generating insert sql and listing columns using a VALUES clause (DEFAULT=[]byte(" VALUES ")) ValuesFragment []byte @@ -576,7 +576,7 @@ func (me *DefaultAdapter) LiteralNil(buf *SqlBuilder) error { return nil } -//Generates SQL bool literal, (e.g. TRUE, FALSE, mysql 1, 0) +//Generates SQL bool literal, (e.g. TRUE, FALSE, mysql 1, 0, sqlite3 1, 0) func (me *DefaultAdapter) LiteralBool(buf *SqlBuilder, b bool) error { if buf.IsPrepared { return me.PlaceHolderSql(buf, b) diff --git a/expressions.go b/expressions.go index c1187cb6..b2421bc9 100644 --- a/expressions.go +++ b/expressions.go @@ -562,6 +562,7 @@ func (me identifier) Clone() Expression { //Sets the table on the current identifier // I("col").Table("table") -> "table"."col" //postgres // I("col").Table("table") -> `table`.`col` //mysql +// I("col").Table("table") -> `table`.`col` //sqlite3 func (me identifier) Table(table string) IdentifierExpression { ret := me.clone() if s, ok := me.col.(string); ok && s != "" && me.table == "" && me.schema == "" { @@ -579,6 +580,7 @@ func (me identifier) GetTable() string { //Sets the table on the current identifier // I("table").Schema("schema") -> "schema"."table" //postgres // I("col").Schema("table") -> `schema`.`table` //mysql +// I("col").Schema("table") -> `schema`.`table` //sqlite3 func (me identifier) Schema(schema string) IdentifierExpression { ret := me.clone() ret.schema = schema @@ -592,6 +594,7 @@ func (me identifier) GetSchema() string { //Sets the table on the current identifier // I("table").Col("col") -> "table"."col" //postgres // I("table").Schema("col") -> `table`.`col` //mysql +// I("table").Schema("col") -> `table`.`col` //sqlite3 func (me identifier) Col(col interface{}) IdentifierExpression { ret := me.clone() if s, ok := me.col.(string); ok && s != "" && me.table == "" {