Repository: ariga/atlas
Pull request #1745 (2023-06-15 07:11:45)

sql/sqlite: extract table options (without rowid, strict) from sql

a8m: Please rebase this branch.

Patch:
diff --git a/sql/sqlite/inspect.go b/sql/sqlite/inspect.go
index 788d46eb..650ebacb 100644
--- a/sql/sqlite/inspect.go
+++ b/sql/sqlite/inspect.go
@@ -363,7 +363,7 @@ func (i *inspect) tables(ctx context.Context, opts *schema.InspectOptions) ([]*s
 		query = tablesQuery
 	)
 	if opts != nil && len(opts.Tables) > 0 {
-		query += " AND name IN (" + strings.Repeat("?, ", len(opts.Tables)-1) + "?)"
+		query += " AND sqlite_master.name IN (" + strings.Repeat("?, ", len(opts.Tables)-1) + "?)"
 		for _, s := range opts.Tables {
 			args = append(args, s)
 		}
@@ -375,8 +375,11 @@ func (i *inspect) tables(ctx context.Context, opts *schema.InspectOptions) ([]*s
 	defer rows.Close()
 	var tables []*schema.Table
 	for rows.Next() {
-		var name, stmt string
-		if err := rows.Scan(&name, &stmt); err != nil {
+		var (
+			name, stmt string
+			wr, strict sql.NullBool
+		)
+		if err := rows.Scan(&name, &stmt, &wr, &strict); err != nil {
 			return nil, fmt.Errorf("sqlite: scanning table: %w", err)
 		}
 		stmt = strings.TrimSpace(stmt)
@@ -386,9 +389,12 @@ func (i *inspect) tables(ctx context.Context, opts *schema.InspectOptions) ([]*s
 				&CreateStmt{S: strings.TrimSpace(stmt)},
 			},
 		}
-		if strings.HasSuffix(stmt, "WITHOUT ROWID") || strings.HasSuffix(stmt, "without rowid") {
+		if wr.Bool {
 			t.Attrs = append(t.Attrs, &WithoutRowID{})
 		}
+		if strict.Bool {
+			t.Attrs = append(t.Attrs, &Strict{})
+		}
 		tables = append(tables, t)
 	}
 	return tables, nil
@@ -462,6 +468,12 @@ type (
 		schema.Attr
 	}
 
+	// Strict describes the `STRICT` configuration.
+	// See: https://sqlite.org/stricttables.html
+	Strict struct {
+		schema.Attr
+	}
+
 	// IndexPredicate describes a partial index predicate.
 	// See: https://www.sqlite.org/partialindex.html
 	IndexPredicate struct {
@@ -715,7 +727,17 @@ const (
 	databasesQuery     = "SELECT `name`, `file` FROM pragma_database_list() WHERE `name` <> 'temp'"
 	databasesQueryArgs = "SELECT `name`, `file` FROM pragma_database_list() WHERE `name` IN (%s)"
 	// Query to list database tables.
-	tablesQuery = "SELECT `name`, `sql` FROM sqlite_master WHERE `type` = 'table' AND `name` NOT LIKE 'sqlite_%' AND `name` NOT LIKE 'libsql_%'"
+	tablesQuery = `
+SELECT
+	sqlite_master.name, sqlite_master.sql, wr, strict
+FROM
+	sqlite_master
+	JOIN pragma_table_list(sqlite_master.name)
+WHERE
+	sqlite_master.type = 'table'
+	AND sqlite_master.name NOT LIKE 'sqlite_%'
+	AND sqlite_master.name NOT LIKE 'libsql_%'
+`
 	// Query to list table information.
 	columnsQuery = "SELECT `name`, `type`, (not `notnull`) AS `nullable`, `dflt_value`, (`pk` <> 0) AS `pk`, `hidden` FROM pragma_table_xinfo('%s') ORDER BY `cid`"
 	// Query to list table indexes.


Test patch:
diff --git a/sql/sqlite/inspect_test.go b/sql/sqlite/inspect_test.go
index f5cb1733..16a8cd42 100644
--- a/sql/sqlite/inspect_test.go
+++ b/sql/sqlite/inspect_test.go
@@ -245,6 +245,37 @@ CREATE TABLE users(
 				require.Equal(t.Attrs[1:], checks)
 			},
 		},
+		{
+			name: "table options",
+			before: func(m mock) {
+				m.ExpectQuery(sqltest.Escape(fmt.Sprintf(databasesQueryArgs, "?"))).
+					WithArgs("main").
+					WillReturnRows(sqltest.Rows(`
+ name |   file
+------+-----------
+ main |
+`))
+				rows := sqlmock.NewRows([]string{"name", "sql", "wr", "strict"})
+				rows.AddRow("users", "CREATE TABLE users(id INTEGER PRIMARY KEY) without rowid, strict", 1, 1)
+				m.ExpectQuery(sqltest.Escape(tablesQuery + " AND sqlite_master.name IN (?)")).
+					WithArgs("users").
+					WillReturnRows(rows)
+				m.ExpectQuery(sqltest.Escape(fmt.Sprintf(columnsQuery, "users"))).
+					WillReturnRows(sqltest.Rows(`
+ name |   type       | nullable | dflt_value  | primary  | hidden
+------+--------------+----------+ ------------+----------+----------
+ id   | int          |  0       |             |  1       |  0
+`))
+				m.noIndexes("users")
+				m.noFKs("users")
+			},
+			expect: func(require *require.Assertions, t *schema.Table, err error) {
+				require.NoError(err)
+				require.Len(t.Attrs, 3)
+				require.Equal(t.Attrs[1], &WithoutRowID{})
+				require.Equal(t.Attrs[2], &Strict{})
+			},
+		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
@@ -551,11 +582,11 @@ func (m mock) tableExists(table string, exists bool, stmt ...string) {
 ------+-----------
  main |   
 `))
-	rows := sqlmock.NewRows([]string{"name", "sql"})
+	rows := sqlmock.NewRows([]string{"name", "sql", "wr", "strict"})
 	if exists {
-		rows.AddRow(table, stmt[0])
+		rows.AddRow(table, stmt[0], nil, nil)
 	}
-	m.ExpectQuery(sqltest.Escape(tablesQuery + " AND name IN (?)")).
+	m.ExpectQuery(sqltest.Escape(tablesQuery + " AND sqlite_master.name IN (?)")).
 		WithArgs(table).
 		WillReturnRows(rows)
 }