-
-
Notifications
You must be signed in to change notification settings - Fork 6
Implement SQLite functions and aggregates, update SQLite encoder and decoder APIs #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
bf0eaef
b025e2f
ff7dcee
0109bf1
20420d2
18bf7c3
e9cbfb1
9239a12
72b763b
6e1384c
e264dcc
51c9c99
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,7 +7,9 @@ import Gren.Kernel.Json exposing (wrap, unwrap) | |
| import Json.Decode as Decode exposing (decodeValue) | ||
| import Result exposing (isOk) | ||
| import Sqlite.Encode as SqliteEncode exposing (toJson) | ||
| import Sqlite.Encode.Row as SqliteEncodeRow exposing (toJson) | ||
| import Sqlite.Decode as SqliteDecode exposing (toJson) | ||
| import Sqlite.Aggregate as SqliteAggregate exposing (Entering, Exiting) | ||
| import Maybe exposing (Just, Nothing) | ||
|
|
||
| */ | ||
|
|
@@ -52,6 +54,75 @@ var _Sqlite_close = function (db) { | |
| }); | ||
| }; | ||
|
|
||
| var _Sqlite_function = F3(function (name, func, db) { | ||
| return __Scheduler_binding(function (callback) { | ||
| try { | ||
| const options = { | ||
| deterministic: true, | ||
| directOnly: true, | ||
| useBigIntArguments: false, | ||
| varargs: true, | ||
| }; | ||
| const wrappedFunc = function (...args) { | ||
| const jsonArgs = args.map((v) => __Json_wrap(v)); | ||
| const result = func(jsonArgs); | ||
| if (__Result_isOk(result)) { | ||
| // The triple `.a` gets the OK result, the SQLite encode | ||
| // value, and then grabs the actual value that needs to | ||
| // be returned | ||
| return result.a.a.a; | ||
| } else { | ||
| return null; | ||
| } | ||
| }; | ||
| db.function(name, options, wrappedFunc); | ||
| callback(__Scheduler_succeed({})); | ||
| } catch (e) { | ||
| callback(_Sqlite_constructError(e)); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| var _Sqlite_aggregate = F5(function (name, init, func, result, db) { | ||
| return __Scheduler_binding(function (callback) { | ||
| try { | ||
| const wrappedFunc = function (direction) { | ||
| var env = __SqliteAggregate_Entering; | ||
| if (direction == "inverse") { | ||
| env = __SqliteAggregate_Exiting; | ||
| } | ||
| return function (state, ...args) { | ||
| const jsonArgs = args.map((v) => __Json_wrap(v)); | ||
| const result = A3(func, env, state, jsonArgs); | ||
| if (__Result_isOk(result)) { | ||
| return result.a; | ||
| } else { | ||
| return null; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same thing here. When
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @robinheghan I can do a little more investigation on this, but my understanding is that because this function (and the one above) is called by SQLite outside of the Gren runtime, it can't interact with the Gren runtime. If we It's easy enough for me to try, though. I'll do so when I get the chance and report back. Another option we can do here is allow the user to pass an optional
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This explination makes sense. See what happens if you throw an exception, and leave the code as is if it doesn't work out.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @robinheghan Tried to get this working tonight, but running into odd behavior. Throwing properly bubbles up the error when done in Do we want for custom SQLite functions to properly return errors, or have both the custom functions and aggregates do the same thing (return |
||
| } | ||
| }; | ||
| }; | ||
| const options = { | ||
| deterministic: true, | ||
| directOnly: true, | ||
| useBigIntArguments: false, | ||
| varargs: true, | ||
| start: () => { | ||
| return init; | ||
| }, | ||
| step: wrappedFunc("step"), | ||
| result: (state) => { | ||
| return result(state).a.a; | ||
| }, | ||
| inverse: wrappedFunc("inverse"), | ||
| }; | ||
| db.aggregate(name, options); | ||
| callback(__Scheduler_succeed({})); | ||
| } catch (e) { | ||
| callback(_Sqlite_constructError(e)); | ||
| } | ||
| }); | ||
| }); | ||
|
|
||
| var _Sqlite_backup = F3(function (destination, pages, db) { | ||
| return __Scheduler_binding(function (callback) { | ||
| try { | ||
|
|
@@ -78,7 +149,9 @@ var _Sqlite_foldl = F4(function (query, db, func, acc) { | |
| try { | ||
| var acc_ = acc; | ||
| const prepped = db.prepare(query.__$query); | ||
| const params = __Json_unwrap(__SqliteEncode_toJson(query.__$parameters)); | ||
| const params = __Json_unwrap( | ||
| __SqliteEncodeRow_toJson(query.__$parameters), | ||
| ); | ||
| const rowDecoder = __SqliteDecode_toJson(query.__$rowDecoder); | ||
|
|
||
| for (const value of prepped.iterate(params)) { | ||
|
|
@@ -107,7 +180,9 @@ var _Sqlite_getMaybeOne = F2(function (query, db) { | |
| return __Scheduler_binding(function (callback) { | ||
| try { | ||
| const prepped = db.prepare(query.__$query); | ||
| const params = __Json_unwrap(__SqliteEncode_toJson(query.__$parameters)); | ||
| const params = __Json_unwrap( | ||
| __SqliteEncodeRow_toJson(query.__$parameters), | ||
| ); | ||
| const rowDecoder = __SqliteDecode_toJson(query.__$rowDecoder); | ||
| const iterator = prepped.iterate(params); | ||
|
|
||
|
|
@@ -149,7 +224,9 @@ var _Sqlite_executeMany = F3(function (statement, values, db) { | |
| } else { | ||
| for (const val of values) { | ||
| lastResult = prepped.run( | ||
| __Json_unwrap(__SqliteEncode_toJson(statement.__$parameters(val))), | ||
| __Json_unwrap( | ||
| __SqliteEncodeRow_toJson(statement.__$parameters(val)), | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| module Sqlite.Aggregate exposing ( .. ) | ||
|
|
||
| {-|-} | ||
|
|
||
| import Sqlite | ||
| import Sqlite.Decode as Decode | ||
| import Sqlite.Encode as Encode | ||
| import Json.Encode | ||
| import Json.Decode | ||
| import Task exposing ( Task ) | ||
|
|
||
|
|
||
| type Function state = | ||
| Function (Direction -> state -> Array Json.Encode.Value -> Result String state) | ||
|
|
||
|
|
||
| {-|-} | ||
| type Direction | ||
| = Entering | ||
| | Exiting | ||
|
|
||
|
|
||
| {-|-} | ||
| type Aggregate state | ||
| = Aggregate | ||
| { init : state | ||
| , function : Function state | ||
| , result : state -> Encode.Value | ||
| } | ||
|
|
||
|
|
||
| aggregate : { init : state, function: Function state, result : state -> Encode.Value } -> Aggregate state | ||
| aggregate { init, function, result } = | ||
| Aggregate | ||
| { init = init | ||
| , function = function | ||
| , result = result | ||
| } | ||
|
|
||
|
|
||
| {-|-} | ||
| start : (state -> Function state) -> Function state | ||
| start func = | ||
| Function <| \direction state args -> | ||
| when func state is | ||
| Function innerFunc -> | ||
| innerFunc direction state args | ||
|
|
||
|
|
||
| {-|-} | ||
| arg : Decode.Decoder a -> (a -> Function state) -> Function state | ||
| arg decoder func = | ||
| Function <| \direction state args -> | ||
| when Array.popFirst args is | ||
| Just { first = first, rest = rest } -> | ||
| when Json.Decode.decodeValue (Decode.unwrap decoder) first is | ||
| Ok val -> | ||
| when func val is | ||
| Function innerFunc -> | ||
| innerFunc direction state rest | ||
|
|
||
| Err _err -> | ||
| Err "decoding error" | ||
|
|
||
| Nothing -> | ||
| Err "not enough arguments passed" | ||
|
|
||
|
|
||
| {-|-} | ||
| return : (Direction -> state) -> Function state | ||
| return func = | ||
| Function <| \direction _state args -> | ||
| when args is | ||
| [] -> | ||
| Ok (func direction) | ||
|
|
||
| many -> | ||
| Err "too many arguments passed" | ||
|
|
||
|
|
||
| {-|-} | ||
| register : String -> Sqlite.Database -> Aggregate state -> Task x {} | ||
| register name db (Aggregate { init, function, result }) = | ||
| let | ||
| (Function unwrappedFunc) = | ||
| function | ||
| in | ||
| Gren.Kernel.Sqlite.aggregate name init unwrappedFunc result db |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to throw an exception here in order to return a detailed
Error? Would be helpful, I think, to see whyfunc(jsonArgs) !== Ok