307 lines
7.2 KiB
Go
307 lines
7.2 KiB
Go
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()
|
|
}
|