feat: ✨ Added Sqlite reader
This commit is contained in:
306
pkg/readers/sqlite/queries.go
Normal file
306
pkg/readers/sqlite/queries.go
Normal file
@@ -0,0 +1,306 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user