package sqlite import ( "fmt" "strings" "git.warky.dev/wdevs/relspecgo/pkg/models" ) // queryTables retrieves all tables from the SQLite database func (r *Reader) queryTables() ([]*models.Table, error) { query := ` SELECT name FROM sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name ` rows, err := r.db.QueryContext(r.ctx, query) if err != nil { return nil, err } defer rows.Close() tables := make([]*models.Table, 0) for rows.Next() { var tableName string if err := rows.Scan(&tableName); err != nil { return nil, err } table := models.InitTable(tableName, "main") tables = append(tables, table) } return tables, rows.Err() } // queryViews retrieves all views from the SQLite database func (r *Reader) queryViews() ([]*models.View, error) { query := ` SELECT name, sql FROM sqlite_master WHERE type = 'view' ORDER BY name ` rows, err := r.db.QueryContext(r.ctx, query) if err != nil { return nil, err } defer rows.Close() views := make([]*models.View, 0) for rows.Next() { var viewName string var sql *string if err := rows.Scan(&viewName, &sql); err != nil { return nil, err } view := models.InitView(viewName, "main") if sql != nil { view.Definition = *sql } views = append(views, view) } return views, rows.Err() } // queryColumns retrieves all columns for a given table or view func (r *Reader) queryColumns(tableName string) (map[string]*models.Column, error) { query := fmt.Sprintf("PRAGMA table_info(%s)", tableName) rows, err := r.db.QueryContext(r.ctx, query) if err != nil { return nil, err } defer rows.Close() columns := make(map[string]*models.Column) for rows.Next() { var cid int var name, dataType string var notNull, pk int var defaultValue *string if err := rows.Scan(&cid, &name, &dataType, ¬Null, &defaultValue, &pk); err != nil { return nil, err } column := models.InitColumn(name, tableName, "main") column.Type = r.mapDataType(strings.ToUpper(dataType)) column.NotNull = (notNull == 1) column.IsPrimaryKey = (pk > 0) column.Sequence = uint(cid + 1) if defaultValue != nil { column.Default = *defaultValue } // Check for autoincrement (SQLite uses INTEGER PRIMARY KEY AUTOINCREMENT) if pk > 0 && strings.ToUpper(dataType) == "INTEGER" { column.AutoIncrement = r.isAutoIncrement(tableName, name) } columns[name] = column } return columns, rows.Err() } // isAutoIncrement checks if a column is autoincrement func (r *Reader) isAutoIncrement(tableName, columnName string) bool { // Check sqlite_sequence table or parse CREATE TABLE statement query := ` SELECT sql FROM sqlite_master WHERE type = 'table' AND name = ? ` var sql string err := r.db.QueryRowContext(r.ctx, query, tableName).Scan(&sql) if err != nil { return false } // Check if the SQL contains AUTOINCREMENT for this column return strings.Contains(strings.ToUpper(sql), strings.ToUpper(columnName)+" INTEGER PRIMARY KEY AUTOINCREMENT") || strings.Contains(strings.ToUpper(sql), strings.ToUpper(columnName)+" INTEGER AUTOINCREMENT") } // queryPrimaryKey retrieves the primary key constraint for a table func (r *Reader) queryPrimaryKey(tableName string) (*models.Constraint, error) { query := fmt.Sprintf("PRAGMA table_info(%s)", tableName) rows, err := r.db.QueryContext(r.ctx, query) if err != nil { return nil, err } defer rows.Close() var pkColumns []string for rows.Next() { var cid int var name, dataType string var notNull, pk int var defaultValue *string if err := rows.Scan(&cid, &name, &dataType, ¬Null, &defaultValue, &pk); err != nil { return nil, err } if pk > 0 { pkColumns = append(pkColumns, name) } } if len(pkColumns) == 0 { return nil, nil } // Create primary key constraint constraintName := fmt.Sprintf("%s_pkey", tableName) constraint := models.InitConstraint(constraintName, models.PrimaryKeyConstraint) constraint.Schema = "main" constraint.Table = tableName constraint.Columns = pkColumns return constraint, rows.Err() } // queryForeignKeys retrieves all foreign key constraints for a table func (r *Reader) queryForeignKeys(tableName string) ([]*models.Constraint, error) { query := fmt.Sprintf("PRAGMA foreign_key_list(%s)", tableName) rows, err := r.db.QueryContext(r.ctx, query) if err != nil { return nil, err } defer rows.Close() // Group foreign keys by id (since composite FKs have multiple rows) fkMap := make(map[int]*models.Constraint) for rows.Next() { var id, seq int var referencedTable, fromColumn, toColumn string var onUpdate, onDelete, match string if err := rows.Scan(&id, &seq, &referencedTable, &fromColumn, &toColumn, &onUpdate, &onDelete, &match); err != nil { return nil, err } if _, exists := fkMap[id]; !exists { constraintName := fmt.Sprintf("%s_%s_fkey", tableName, referencedTable) if id > 0 { constraintName = fmt.Sprintf("%s_%s_fkey_%d", tableName, referencedTable, id) } constraint := models.InitConstraint(constraintName, models.ForeignKeyConstraint) constraint.Schema = "main" constraint.Table = tableName constraint.ReferencedSchema = "main" constraint.ReferencedTable = referencedTable constraint.OnUpdate = onUpdate constraint.OnDelete = onDelete constraint.Columns = []string{} constraint.ReferencedColumns = []string{} fkMap[id] = constraint } // Add column to the constraint fkMap[id].Columns = append(fkMap[id].Columns, fromColumn) fkMap[id].ReferencedColumns = append(fkMap[id].ReferencedColumns, toColumn) } // Convert map to slice foreignKeys := make([]*models.Constraint, 0, len(fkMap)) for _, fk := range fkMap { foreignKeys = append(foreignKeys, fk) } return foreignKeys, rows.Err() } // queryIndexes retrieves all indexes for a table func (r *Reader) queryIndexes(tableName string) ([]*models.Index, error) { query := fmt.Sprintf("PRAGMA index_list(%s)", tableName) rows, err := r.db.QueryContext(r.ctx, query) if err != nil { return nil, err } defer rows.Close() indexes := make([]*models.Index, 0) for rows.Next() { var seq int var name string var unique int var origin string var partial int if err := rows.Scan(&seq, &name, &unique, &origin, &partial); err != nil { return nil, err } // Skip auto-generated indexes (origin = 'pk' for primary keys, etc.) // origin: c = CREATE INDEX, u = UNIQUE constraint, pk = PRIMARY KEY if origin == "pk" || origin == "u" { continue } index := models.InitIndex(name, tableName, "main") index.Unique = (unique == 1) // Get index columns columns, err := r.queryIndexColumns(name) if err != nil { return nil, err } index.Columns = columns indexes = append(indexes, index) } return indexes, rows.Err() } // queryIndexColumns retrieves the columns for a specific index func (r *Reader) queryIndexColumns(indexName string) ([]string, error) { query := fmt.Sprintf("PRAGMA index_info(%s)", indexName) rows, err := r.db.QueryContext(r.ctx, query) if err != nil { return nil, err } defer rows.Close() columns := make([]string, 0) for rows.Next() { var seqno, cid int var name *string if err := rows.Scan(&seqno, &cid, &name); err != nil { return nil, err } if name != nil { columns = append(columns, *name) } } return columns, rows.Err() }