Skip to content

Commit

Permalink
type checking done, F# project generation, fix mig log
Browse files Browse the repository at this point in the history
  • Loading branch information
lamg committed Jun 28, 2024
1 parent b08599e commit 7ce4e50
Show file tree
Hide file tree
Showing 20 changed files with 216 additions and 31 deletions.
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.0.18] - 2024-06-28

Added:

- Basic SQL type checking for project schema
- F# project generation with `selectAll` queries for all views and tables

Fixed:

- `mig relations` command.
- `mig log` command when SQLite file does not exists
10 changes: 9 additions & 1 deletion Cli/Program.fs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type MigArgs =
| [<CliPrefix(CliPrefix.None)>] DbSchema of ParseResults<DumpSchemaArgs>
| [<CliPrefix(CliPrefix.None)>] Relations of ParseResults<RelationsArgs>
| [<CliPrefix(CliPrefix.None)>] Export of ParseResults<ExportArgs>
| [<CliPrefix(CliPrefix.None)>] GenFs of ParseResults<GenFsArgs>
| [<AltCommandLine("-p")>] ProjectPath of path: string

interface IArgParserTemplate with
Expand All @@ -46,6 +47,13 @@ type MigArgs =
| Relations _ -> "shows the relations (tables + views) type signatures in the database or project"
| Export _ -> "exports the content of a relation as an insert statement"
| ProjectPath _ -> "project path"
| GenFs _ -> "generates an F# project with queries to the database"

and GenFsArgs =
| [<NoCommandLine>] DummyGenFs

interface IArgParserTemplate with
member _.Usage = "generates an F# project with queries to the database"

and VersionArgs =
| [<NoCommandLine>] Dummy
Expand Down Expand Up @@ -224,7 +232,6 @@ let main (args: string array) =

match Lib.loadProjectFromDir path with
| Ok p ->

match command with
| Some(DbSchema flags) -> dumpSchema p flags
| Some(Commit flags) -> commit p flags
Expand All @@ -238,6 +245,7 @@ let main (args: string array) =
Assembly.GetExecutingAssembly().GetName().Version.ToString() |> printfn "%s"
0
| Some(Export args) -> exportRelation p args
| Some(GenFs _) -> Cli.generateFsProj p
| _ ->
Print.printRed "no command given"
1
Expand Down
3 changes: 3 additions & 0 deletions Lib/Cli.fs
Original file line number Diff line number Diff line change
Expand Up @@ -317,3 +317,6 @@ let exportRelation (p: Project) (relation: string) =
| None ->
Print.printError $"relation {relation} not found"
1

let generateFsProj (p: Project) =
FsGeneration.Main.generateDatabaseProj None p
3 changes: 2 additions & 1 deletion Lib/DbProject/BuildProject.fs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ let mergeTomlSql (p: DbTomlFile) (src: SqlFile) =
inits = p.inits
reports = p.reports
pullScript = p.pullScript
schemaVersion = p.schemaVersion }
schemaVersion = p.schemaVersion
includeFsFiles = p.includeFsFiles }

let buildProject (reader: string -> string) (p: DbTomlFile) =
let parse (file, sql) =
Expand Down
8 changes: 7 additions & 1 deletion Lib/DbProject/ParseDbToml.fs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ let tableInit = "table_init"
[<Literal>]
let reportTable = "report"

[<Literal>]
let includeFsFiles = "include_fs_files"

let tryGet (t: Tomlyn.Model.TomlTable) (key: string) =
if t.ContainsKey(key) then Some(t[key]) else None

Expand Down Expand Up @@ -118,6 +121,8 @@ let parseDbToml (source: string) =
| Some v -> v
| _ -> MalformedProject $"no {versionRemarks} field defined in db.toml" |> raise

let included = tryGetArray doc includeFsFiles

match tryGetString doc dbFileKey with
| None -> MalformedProject $"no {dbFileKey} defined" |> raise
| Some f ->
Expand All @@ -130,7 +135,8 @@ let parseDbToml (source: string) =
inits = inits
pullScript = script
schemaVersion = version
versionRemarks = remarks }
versionRemarks = remarks
includeFsFiles = included }

let parseDbTomlFile (path: string) =
try
Expand Down
11 changes: 1 addition & 10 deletions Lib/DbUtil.fs
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,4 @@ let loadFromRes (asm: Assembly) (namespaceForResx: string) (file: string) =
(namespaceDotFile, file.ReadToEnd())
with ex ->
FailedLoadResFile $"failed loading resource file {namespaceDotFile}: {ex.Message}"
|> raise

type ReaderExecuter =
abstract member ExecuteReader: string -> IDataReader

type SqliteReaderExecuter(connection: SqliteConnection, transaction: SqliteTransaction) =
interface ReaderExecuter with
member _.ExecuteReader(sql: string) =
let command = new SqliteCommand(sql, connection, transaction)
command.ExecuteReader()
|> raise
20 changes: 11 additions & 9 deletions Lib/Execution/Store/Get.fs
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,17 @@ let getStepReason (conn: SqliteConnection) (migrationId: int64) (stepIndex: int6
/// <param name="conn"></param>
let getMigrations (conn: SqliteConnection) =
let migrations =
select {
for m in migrationTable do
orderByDescending m.date
}
|> conn.SelectAsync<StoredMigration>
|> Async.AwaitTask
|> Async.RunSynchronously
|> Seq.toList

try
select {
for m in migrationTable do
orderByDescending m.date
}
|> conn.SelectAsync<StoredMigration>
|> Async.AwaitTask
|> Async.RunSynchronously
|> Seq.toList
with :? System.AggregateException as _ ->
[]

migrations
|> List.map (fun (m: StoredMigration) ->
Expand Down
54 changes: 54 additions & 0 deletions Lib/FsGeneration/FsprojFile.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2023 Luis Ángel Méndez Gort

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

module internal Migrate.FsGeneration.FsprojFile

open System.IO
open System.Xml.Linq
open Migrate.Types

let projectToFsproj (p: Project) =

let includeQuery =
XElement(XName.Get "Compile", XAttribute(XName.Get "Include", "Query.fs"))

let fs =
p.includeFsFiles
|> List.map (fun f -> XElement(XName.Get "Compile", XAttribute(XName.Get "Include", f)))

XElement(
XName.Get "Project",
XAttribute(XName.Get "Sdk", "Microsoft.NET.Sdk"),

XElement(XName.Get "PropertyGroup", XElement(XName.Get "TargetFramework", "net8.0")),

XElement(XName.Get "ItemGroup", includeQuery :: fs),

XElement(
XName.Get "ItemGroup",
XElement(
XName.Get "PackageReference",
XAttribute(XName.Get "Include", "Microsoft.Data.Sqlite"),
XAttribute(XName.Get "Version", "8.0.6")
),
XElement(
XName.Get "PackageReference",
XAttribute(XName.Get "Include", "MigrateLib"),
XAttribute(XName.Get "Version", "0.0.18")
)
)
)

let saveXmlTo (dir: string) (xml: XElement) =
Path.Join(dir, "Database.fsproj") |> xml.Save
36 changes: 36 additions & 0 deletions Lib/FsGeneration/Main.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Copyright 2023 Luis Ángel Méndez Gort

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

module internal Migrate.FsGeneration.Main

open System.IO
open Migrate.Types
open Migrate.Checks.Types

let generateDatabaseProj (dir: string option) (p: Project) =
let dir = Option.defaultValue (Directory.GetCurrentDirectory()) dir
p |> FsprojFile.projectToFsproj |> FsprojFile.saveXmlTo dir
let rs, errs = typeCheck p.source

match errs with
| [] ->
let queryFs =
rs |> relationTypes |> QueryModule.queryModule |> QueryModule.toFsString

let queryPath = Path.Join(dir, "Query.fs")
File.WriteAllText(queryPath, queryFs)
1
| _ ->
errs |> String.concat "\n" |> LamgEnv.errPrint
0
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

module Migrate.FsGeneration.FsGeneration
module internal Migrate.FsGeneration.QueryModule

open System
open Migrate.Types
Expand Down Expand Up @@ -87,7 +87,8 @@ let queryModule (rs: Relation list) =

Oak() {
TopLevelModule "Database.Query" {
yield Open "Migrate.DbUtil"
yield Open "System"
yield Open "Migrate.FsGeneration.Util"

for x in rs |> List.map relationToFsRecord do
yield x
Expand Down
27 changes: 27 additions & 0 deletions Lib/FsGeneration/Util.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright 2023 Luis Ángel Méndez Gort

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

module Migrate.FsGeneration.Util

open System.Data
open Microsoft.Data.Sqlite

type ReaderExecuter =
abstract member ExecuteReader: string -> IDataReader

type SqliteReaderExecuter(connection: SqliteConnection, transaction: SqliteTransaction) =
interface ReaderExecuter with
member _.ExecuteReader(sql: string) =
let command = new SqliteCommand(sql, connection, transaction)
command.ExecuteReader()
23 changes: 20 additions & 3 deletions Lib/Lib.fs
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
// Copyright 2023 Luis Ángel Méndez Gort

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

module Migrate.Lib

/// <summary>
/// Convenience function for running commits when using Migrate as a library
/// </summary>
let commitQuiet (p: Types.Project) =
Migrate.Execution.Commit.migrateAndCommit p true
Execution.Commit.migrateAndCommit p true

/// <summary>
/// Convenience function for loading project files from assembly resources
/// Raises MalformedProject in case of failure
/// </summary>
let loadResourceFile (asm: System.Reflection.Assembly) (prefix: string) (file: string) =
Migrate.DbProject.LoadProjectFiles.loadResourceFile asm prefix file
DbProject.LoadProjectFiles.loadResourceFile asm prefix file

/// <summary>
/// Loads a project using a custom file reader
/// </summary>
let loadProjectWith (loadFile: string -> string) =
Migrate.DbProject.LoadProjectFiles.loadProjectWith loadFile
DbProject.LoadProjectFiles.loadProjectWith loadFile

/// <summary>
/// Loads a project from a directory if specified or the current one instead
Expand All @@ -27,3 +41,6 @@ let loadProjectFromDir (dir: string option) =
DbProject.LoadProjectFiles.loadProjectFromDir dir |> Ok
with e ->
Error e.Message

let generateDatabaseProj (dir: string option) (p: Types.Project) =
FsGeneration.Main.generateDatabaseProj dir p
12 changes: 9 additions & 3 deletions Lib/Lib.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,11 @@
<Compile Include="Execution\Store\Amend.fs"/>
<Compile Include="Execution\Store\Print.fs"/>
<Compile Include="Execution\Commit.fs"/>
<Compile Include="Checks\Types.fs" />
<Compile Include="FsGeneration\FsGeneration.fs" />
<Compile Include="Checks\Types.fs"/>
<Compile Include="FsGeneration\Util.fs"/>
<Compile Include="FsGeneration\QueryModule.fs"/>
<Compile Include="FsGeneration\FsprojFile.fs"/>
<Compile Include="FsGeneration\Main.fs"/>
<Compile Include="Reports\Report.fs"/>
<Compile Include="Reports\RelationsSummary.fs"/>
<Compile Include="Reports\Export.fs"/>
Expand All @@ -63,6 +66,9 @@
<None Include="..\README.md" Pack="true" PackagePath="\"/>
<None Include="..\doc\images\logo.png" Pack="true" PackagePath="\"/>
</ItemGroup>

<ItemGroup>
<PackageReference Include="LamgEnv" Version="0.0.2"/>
</ItemGroup>

<Import Project="..\.paket\Paket.Restore.targets"/>
</Project>
7 changes: 7 additions & 0 deletions Lib/Types.fs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ type Report = { src: string; dest: string }
type Project =
{ dbFile: string
source: SqlFile
includeFsFiles: string list
syncs: string list
inits: string list
reports: Report list
Expand Down Expand Up @@ -124,6 +125,12 @@ type DbTomlFile =
/// Remarks about the version
/// </summary>
versionRemarks: string

/// <summary>
/// F# files in the database project directory written by the user,
/// to be included in the generated Database.fsproj file
/// </summary>
includeFsFiles: string list
}

type SqlStep = { sql: string; error: string option }
Expand Down
3 changes: 2 additions & 1 deletion Test/Calculation.fs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ let emptyProject =
syncs = []
inits = []
reports = []
pullScript = None }
pullScript = None
includeFsFiles = [] }

let schemaWithOneTable (tableName: string) =
{ emptySchema with
Expand Down
1 change: 1 addition & 0 deletions Test/CheckTypes.fs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ let exampleProject =
syncs = []
inits = []
pullScript = None
includeFsFiles = []
source =
{ tables =
[ { name = "table0"
Expand Down
Loading

0 comments on commit 7ce4e50

Please sign in to comment.