Skip to content

Commit

Permalink
support for tables with initial values
Browse files Browse the repository at this point in the history
  • Loading branch information
lamg committed Jan 16, 2024
1 parent eb185e6 commit 8f6ebef
Show file tree
Hide file tree
Showing 24 changed files with 227 additions and 90 deletions.
2 changes: 1 addition & 1 deletion Cli/Cli.fsproj
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>0.0.14</Version>
<Version>0.0.15</Version>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<RollForward>major</RollForward>
Expand Down
15 changes: 14 additions & 1 deletion Lib/Calculation/Migration.fs
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,26 @@ let constraintsMigration (dbSchema: SqlFile) (p: Project) =
|> List.map (fun (table, left, right) -> Solver.constraints dbSchema.views (findTable p.source table) left right)
|> List.concat

let tableInitsMigration (dbSchema: SqlFile) (p: Project) =
let filterInits =
List.filter (fun (t: InsertInto) -> p.inits |> List.exists (fun x -> x = t.table))

let leftInits = dbSchema.tableInits |> filterInits
let rightInits = p.source.tableInits |> filterInits
let homologousInits = zipHomologous leftInits rightInits (_.table) id

homologousInits
|> List.map (fun (_, left, right) -> Solver.tableInits left right)
|> List.concat

let migration (dbSchema: SqlFile) (p: Project) =
let migrators =
[ tablesMigration
viewsMigration
columnsMigration
constraintsMigration
insertsMigration ]
tableSyncsMigration
tableInitsMigration ]

let findMap (f: 'a -> 'b option) (xs: 'a list) = xs |> Seq.choose f |> Seq.tryHead

Expand Down
9 changes: 8 additions & 1 deletion Lib/Calculation/Solver.fs
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ let constraints (views: CreateView list) (right: CreateTable) (xs: ColumnConstra

createDelete xs ys keySel keySel constraintSolution constraintSolution

let insertInto (keyIndexes: int list) (left: InsertInto) (right: InsertInto) =
let tableSyncs (keyIndexes: int list) (left: InsertInto) (right: InsertInto) =

let selectExpr (indexes: int list) (xs: Expr list) =
indexes
Expand Down Expand Up @@ -170,3 +170,10 @@ let insertInto (keyIndexes: int list) (left: InsertInto) (right: InsertInto) =
(Row.sqlDeleteRow right keyIndexes)
(Row.sqlInsertRow right)
toUpdate

let tableInits (left: InsertInto) (right: InsertInto) =
match left.values with
| [] ->
[ { reason = Diff.Added(right.values |> SqlGeneration.InsertInto.sqlValues)
statements = SqlGeneration.InsertInto.sqlInsertInto right } ]
| _ -> []
4 changes: 2 additions & 2 deletions Lib/Calculation/TableSync.fs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ let reorderList (swaps: int array) (xs: 'a list) =

ys |> Array.toList

let insertsMigration (dbSchema: SqlFile) (p: Project) =
let tableSyncsMigration (dbSchema: SqlFile) (p: Project) =
let key (i: InsertInto) = i.table
let value = id

Expand All @@ -113,5 +113,5 @@ let insertsMigration (dbSchema: SqlFile) (p: Project) =

let primaryKey = table |> findKeyCols |> findKeyIndexes table

Solver.insertInto primaryKey left right)
Solver.tableSyncs primaryKey left right)
|> List.concat
5 changes: 4 additions & 1 deletion Lib/DbProject/BuildProject.fs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ open Migrate.SqlParser
let collectSql (xs: SqlFile list) =
let r =
{ tableSyncs = []
tableInits = []
tables = []
views = []
indexes = [] }
Expand All @@ -28,6 +29,7 @@ let collectSql (xs: SqlFile list) =
|> List.fold
(fun acc n ->
{ tableSyncs = acc.tableSyncs @ n.tableSyncs
tableInits = acc.tableInits @ n.tableInits
tables = acc.tables @ n.tables
views = acc.views @ n.views
indexes = acc.indexes @ n.indexes })
Expand All @@ -38,13 +40,14 @@ let mergeTomlSql (p: DbTomlFile) (src: SqlFile) =
dbFile = p.dbFile
source = src
syncs = p.syncs
inits = p.inits
reports = p.reports
pullScript = p.pullScript
schemaVersion = p.schemaVersion }

let buildProject (reader: string -> string) (p: DbTomlFile) =
let parse (file, sql) =
match parseSql file sql with
match parseSql p.inits file sql with
| Ok p -> p
| Error e -> MalformedProject e |> raise

Expand Down
60 changes: 41 additions & 19 deletions Lib/DbProject/LoadDbSchema.fs
Original file line number Diff line number Diff line change
Expand Up @@ -21,22 +21,33 @@ open Migrate.DbUtil
open Migrate.SqlParser
open Dapper.FSharp.SQLite

let relationValues (conn: SqliteConnection) (relation: string) (cols: string list) (readRow: IDataReader -> Expr list) =
let joinedCols = cols |> Migrate.SqlGeneration.Util.sepComma id

let query = $"SELECT {joinedCols} FROM {relation}"
let valuesFromStatement (conn: SqliteConnection) (selectStatement: string) (readRow: IDataReader -> Expr list) =
let c = conn.CreateCommand()
c.CommandText <- query
c.CommandText <- selectStatement
let rd = c.ExecuteReader()

seq {
while rd.Read() do
let vs = readRow rd
yield vs
}
|> Seq.toList

type RelationReader = SqliteConnection -> string -> string list -> (IDataReader -> Expr list) -> InsertInto

let allRows (conn: SqliteConnection) (relation: string) (cols: string list) (readRow: IDataReader -> Expr list) =
let joinedCols = cols |> Migrate.SqlGeneration.Util.sepComma id
let statement = $"SELECT {joinedCols} FROM {relation}"
let vss = valuesFromStatement conn statement readRow

{ table = relation
columns = cols
values = vss }

let vss =
seq {
while rd.Read() do
let vs = readRow rd
yield vs
}
|> Seq.toList
let justOneRow (conn: SqliteConnection) (relation: string) (cols: string list) (readRow: IDataReader -> Expr list) =
let joinedCols = cols |> Migrate.SqlGeneration.Util.sepComma id
let statement = $"SELECT {joinedCols} FROM {relation} LIMIT 1"
let vss = valuesFromStatement conn statement readRow

{ table = relation
columns = cols
Expand All @@ -49,13 +60,13 @@ let rowReader (xs: SqlType list) (rd: IDataReader) =
| SqlText -> rd.GetString i |> String
| SqlInteger -> rd.GetInt32 i |> Integer)

let tableValues (conn: SqliteConnection) (ct: CreateTable) =
let tableValues (conn: SqliteConnection) (relReader: RelationReader) (ct: CreateTable) =
let cols = ct.columns |> List.map _.name
let types = ct.columns |> List.map _.columnType
let readRow = rowReader types

try
relationValues conn ct.name cols readRow
relReader conn ct.name cols readRow
with :? SqliteException as e ->
if e.Message.Contains "no such table" then
{ table = ct.name
Expand Down Expand Up @@ -102,6 +113,7 @@ let dbSchema (p: Project) (conn: SqliteConnection) =
{ tables = []
views = []
tableSyncs = []
tableInits = []
indexes = [] }

let schema =
Expand All @@ -111,20 +123,30 @@ let dbSchema (p: Project) (conn: SqliteConnection) =
| xs ->
xs
|> joinSql
|> parseSql conn.DataSource
|> parseSql p.inits conn.DataSource
|> function
| Ok f -> f
| Error e -> FailedParse e |> raise

let schemaWithIns =
let schemaWithSyncs =
p.syncs
|> List.choose (fun ts ->
p.source.tables
|> List.tryFind (fun n -> n.name = ts)
|> Option.map (tableValues conn))
|> Option.map (tableValues conn allRows))
|> (fun ins -> { schema with tableSyncs = ins })

schemaWithIns
let schemaWithInits =
p.inits
|> List.choose (fun tableInit ->
p.source.tables
|> List.tryFind (fun n -> n.name = tableInit)
|> Option.map (tableValues conn justOneRow))
|> (fun ins ->
{ schemaWithSyncs with
tableInits = ins })

schemaWithInits

let migrationSchema (conn: SqliteConnection) =
sqliteMasterStatements conn
Expand All @@ -136,7 +158,7 @@ let migrationSchema (conn: SqliteConnection) =
| xs ->
xs
|> joinSql
|> parseSql conn.DataSource
|> parseSql [] conn.DataSource
|> function
| Ok f -> Some f
| Error e -> FailedParse $"Loading migration tables:\n{e}" |> raise
5 changes: 4 additions & 1 deletion Lib/DbProject/ParseDbToml.fs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ let parseDbToml (source: string) =
| Some n -> n
| None -> MalformedProject $"{ctx}: environment variable '{var}' not defined" |> raise

let syncs = tryGetArray doc syncTable
let syncs = tryGetArray doc tableSync

let inits = tryGetArray doc tableInit

let reports =
match tryGetTableArray doc reportTable with
Expand Down Expand Up @@ -125,6 +127,7 @@ let parseDbToml (source: string) =
reports = reports
files = files
syncs = syncs
inits = inits
pullScript = script
schemaVersion = version
versionRemarks = remarks }
Expand Down
9 changes: 7 additions & 2 deletions Lib/Execution/Commit.fs
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,17 @@ let replicateInDb (schema: SqlFile) (dbFile: string) =

let views = schema.views |> List.map View.sqlCreateView

let inserts = schema.tableSyncs |> List.map InsertInto.sqlInsertInto
let tableSyncs = schema.tableSyncs |> List.map InsertInto.sqlInsertInto

let tableInits = schema.tableInits |> List.map InsertInto.sqlInsertInto

let indexes = schema.indexes |> List.map Index.sqlCreateIndex

let sql =
[ tables; views; inserts; indexes ] |> List.concat |> List.concat |> joinSql
[ tables; views; tableSyncs; tableInits; indexes ]
|> List.concat
|> List.concat
|> joinSql

use conn = openConn dbFile
conn.Open()
Expand Down
2 changes: 1 addition & 1 deletion Lib/Execution/Store/Init.fs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ let initStore (conn: SqliteConnection) =
FailedOpenStore e |> raise

let refSchema =
match SqlParser.parseSql dbFile referenceStoreSchema with
match SqlParser.parseSql [] dbFile referenceStoreSchema with
| Ok f -> f
| Error e -> FailedOpenStore e |> raise

Expand Down
4 changes: 2 additions & 2 deletions Lib/Reports/Export.fs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ let findRelation (p: Project) (relation: string) =
let exportTable (p: Project) (table: CreateTable) =
use conn = openConn p.dbFile

LoadDbSchema.tableValues conn table
LoadDbSchema.tableValues conn LoadDbSchema.allRows table
|> SqlGeneration.InsertInto.sqlInsertInto
|> joinSqlPretty

Expand All @@ -41,7 +41,7 @@ let exportView (p: Project) (view: string) =
let rd = rowReader cols
use conn = openConn p.dbFile

LoadDbSchema.relationValues conn view colNames rd
LoadDbSchema.allRows conn view colNames rd
|> SqlGeneration.InsertInto.sqlInsertInto
|> joinSqlPretty

Expand Down
5 changes: 4 additions & 1 deletion Lib/SqlGeneration/InsertInto.fs
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,13 @@ let sqlRowToString (vs: Expr list) =
let sqlColumnNames (i: InsertInto) =
i.columns |> String.concat ", " |> (fun c -> $"({c})")

let sqlValues (vss: Expr list list) =
vss |> List.map sqlRowToString |> String.concat ",\n"

let sqlInsertInto (i: InsertInto) =
match i.values with
| [] -> []
| _ ->
let columns = sqlColumnNames i
let values = i.values |> List.map sqlRowToString |> String.concat ",\n"
let values = i.values |> sqlValues
[ $"INSERT INTO {i.table}{columns} VALUES\n{values}" ]
15 changes: 10 additions & 5 deletions Lib/SqlParser.fs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ open Migrate.Types
open SqlParser.Dialects
open SqlParser.Tokens

let classifyStatement (acc: SqlFile) (s: Statement) =
let classifyStatement (inits: string list) (acc: SqlFile) (s: Statement) =
match box s with
| :? Statement.Insert as s ->
let cols = s.Columns |> Seq.map _.Value |> Seq.toList
Expand All @@ -33,8 +33,12 @@ let classifyStatement (acc: SqlFile) (s: Statement) =
columns = cols
values = vss }

{ acc with
tableSyncs = ins :: acc.tableSyncs }
if List.contains ins.table inits then
{ acc with
tableInits = ins :: acc.tableInits }
else
{ acc with
tableSyncs = ins :: acc.tableSyncs }
| :? Statement.CreateTable as s ->
let cols =
s.Columns
Expand Down Expand Up @@ -121,16 +125,17 @@ let classifyStatement (acc: SqlFile) (s: Statement) =
indexes = index :: acc.indexes }
| _ -> acc

let parseSql (file: string) (sql: string) =
let parseSql (inits: string list) (file: string) (sql: string) =
try
let ast = Parser().ParseSql(sql, SQLiteDialect())

let emptyFile =
{ tables = []
indexes = []
tableSyncs = []
tableInits = []
views = [] }

ast |> Seq.fold classifyStatement emptyFile |> Ok
ast |> Seq.fold (classifyStatement inits) emptyFile |> Ok
with :? ParserException as e ->
Error $"Error parsing {file}({e.Line},{e.Column}): {e.Message}"
7 changes: 6 additions & 1 deletion Lib/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ type CreateIndex =

type SqlFile =
{ tableSyncs: InsertInto list
tableInits: InsertInto list
views: CreateView list
tables: CreateTable list
indexes: CreateIndex list }


type TableSync = { table: string; idCol: int }

type Report = { src: string; dest: string }
Expand All @@ -74,6 +74,7 @@ type Project =
{ dbFile: string
source: SqlFile
syncs: string list
inits: string list
reports: Report list
pullScript: string option
schemaVersion: string
Expand All @@ -96,6 +97,10 @@ type DbTomlFile =
/// </summary>
syncs: string list

/// <summary>
/// List of tables to initialize with insert values if empty
/// </summary>
inits: string list
/// <summary>
/// List of reports (a view and a table that acts as cache for the data the view generates)
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions Test/Calculation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ open Xunit

let emptySchema =
{ tableSyncs = []
tableInits = []
tables = []
views = []
indexes = [] }
Expand All @@ -30,6 +31,7 @@ let emptyProject =
dbFile = "db.sqlite3"
source = emptySchema
syncs = []
inits = []
reports = []
pullScript = None }

Expand Down
Loading

0 comments on commit 8f6ebef

Please sign in to comment.