0 | module Postgres.Data.PostgresTable
2 | import Postgres.Data.PostgresType
3 | import public Data.DPair
5 | import Postgres.Data.PostgresValue
7 | import public Data.List1
8 | import public Data.List.Elem
10 | import Data.Vect.Elem
11 | import public Data.Vect.Quantifiers
12 | import public Data.String
20 | data Ident = Id String
22 | %name Ident
id, id2, id3
26 | show (Id name) = "\"\{name}\""
32 | data Alias = Named String
35 | %name Alias
alias, alias1, alias2
39 | aliasIdentifier : Alias -> Ident
40 | aliasIdentifier (Named str) = Id str
41 | aliasIdentifier (Generated k) = Id "_idr_t_\{show k}"
45 | show = show . aliasIdentifier
49 | (Named str) == (Named str') = str == str'
50 | (Generated k) == (Generated k') = k == k'
58 | toAlias : Ident -> Alias
59 | toAlias (Id str) = Named str
66 | maybeShowAsAlias : Maybe Alias -> String
67 | maybeShowAsAlias Nothing = ""
68 | maybeShowAsAlias (Just a) = " AS \{show a}"
74 | maybeShowAliasColumnPrefix : Maybe Alias -> String
75 | maybeShowAliasColumnPrefix Nothing = ""
76 | maybeShowAliasColumnPrefix (Just a) = "\{show a}."
80 | data TableStatement =
81 | Identifier String (Maybe Alias)
83 | Subquery String Alias
88 | tableAlias : TableStatement -> Maybe Alias
89 | tableAlias (Identifier str a) = a
90 | tableAlias (Subquery str a) = Just a
91 | tableAlias (Fragment str) = Nothing
95 | tableAliasOrName : TableStatement -> Maybe Ident
96 | tableAliasOrName (Identifier name alias) = Just $
maybe (Id name) aliasIdentifier alias
97 | tableAliasOrName (Subquery _ alias) = Just $
aliasIdentifier alias
98 | tableAliasOrName (Fragment _) = Nothing
101 | Show TableStatement where
102 | show (Identifier n a) = "\{show $ Id n}\{maybeShowAsAlias a}"
103 | show (Subquery query a) = "(\{query}) AS \{show a}"
104 | show (Fragment query) = query
108 | named : String -> {default Nothing alias : Maybe Alias} -> TableStatement
109 | named str {alias} = Identifier str alias
113 | subquery : String -> Alias -> TableStatement
114 | subquery = Subquery
133 | record ColumnIdentifier where
134 | constructor MkColumnId
135 | sourceTable : Maybe Alias
139 | replaceSource : Maybe Alias -> ColumnIdentifier -> ColumnIdentifier
140 | replaceSource newSource (MkColumnId _ name) = MkColumnId newSource name
143 | forgetSource : ColumnIdentifier -> ColumnIdentifier
144 | forgetSource = replaceSource Nothing
147 | data AnonymousColumnIdentifier : ColumnIdentifier -> Type where
148 | Anon : AnonymousColumnIdentifier (MkColumnId Nothing _)
151 | Show ColumnIdentifier where
152 | show (MkColumnId sourceTable name) = "\{maybeShowAliasColumnPrefix sourceTable}\{show . Id $ name}"
155 | FromString ColumnIdentifier where
157 | case (split (== '.') str) of
158 | (column ::: []) => MkColumnId Nothing column
159 | (table ::: column@(x :: xs)) => MkColumnId (Just $
Named table) (joinBy "." column)
164 | interface PostgresTable t where
166 | tableStatement : t -> TableStatement
169 | columns : t -> List (ColumnIdentifier, Exists PColType)
171 | as : t -> String -> t
177 | alias : PostgresTable t => (table : t) -> Maybe Alias
178 | alias = tableAlias . tableStatement
184 | aliasOrName : PostgresTable t => (table : t) -> Maybe Alias
185 | aliasOrName = map toAlias . tableAliasOrName . tableStatement
191 | record RuntimeTable where
193 | tableStatement : TableStatement
194 | columns : List (ColumnIdentifier, Exists PColType)
197 | PostgresTable RuntimeTable where
198 | tableStatement = .tableStatement
201 | as (RT (Identifier str x) columns) a =
202 | RT (Identifier str (Just $
Named a))
203 | (mapFst (replaceSource . Just $
Named a) <$> columns)
204 | as (RT (Subquery str x) columns) a =
205 | RT (Subquery str (Named a))
206 | (mapFst (replaceSource . Just $
Named a) <$> columns)
207 | as (RT (Fragment str) columns) a =
208 | RT (Subquery "SELECT * FROM \{str}" (Named a))
209 | (mapFst (replaceSource . Just $
Named a) <$> columns)
213 | record PersistedTable where
216 | {default Nothing alias : Maybe Alias}
217 | columns : List (String, Exists PColType)
219 | namespace PersistedTable
221 | aliasOrName : (table : PersistedTable) -> Alias
222 | aliasOrName (PT tableName {alias} columns) = maybe (toAlias $
Id tableName) id alias
225 | PostgresTable PersistedTable where
226 | columns table = mapFst (MkColumnId (Just . aliasOrName $
table)) <$> table.columns
228 | tableStatement (PT n {alias} _) = named n {alias}
230 | as (PT tableName columns) a = PT tableName {alias=Just $
Named a} columns
235 | col : (nullable : Nullability) -> (pt : PType) -> Exists PColType
236 | col nullable pt = Evidence pt (MkColType nullable pt)
248 | pgTable : (name : String)
249 | -> (columns : List (String, Nullability, PType))
251 | pgTable name columns = PT name (mapSnd (uncurry col) <$> columns)
261 | data LooseColumnMapping : (0 _ : PType -> Type -> Type)
262 | -> List (ColumnIdentifier, Exists PColType)
263 | -> (ColumnIdentifier, Type)
265 | HereNul : (ident : ColumnIdentifier) -> (ty : Type) -> castTy pt ty => LooseColumnMapping castTy ((ident, (Evidence pt (MkColType Nullable pt))) :: xs) (ident, Maybe ty)
266 | HereNulLoose : (columnName : String) -> (ty : Type) -> castTy pt ty => LooseColumnMapping castTy ((MkColumnId _ columnName, (Evidence pt (MkColType Nullable pt))) :: xs) (MkColumnId Nothing columnName, Maybe ty)
267 | Here : (ident : ColumnIdentifier) -> (ty : Type) -> castTy pt ty => LooseColumnMapping castTy ((ident, (Evidence pt (MkColType NonNullable pt))) :: xs) (ident, ty)
268 | HereLoose : (columnName : String) -> (ty : Type) -> castTy pt ty => LooseColumnMapping castTy ((MkColumnId _ columnName, (Evidence pt (MkColType NonNullable pt))) :: xs) (MkColumnId Nothing columnName, ty)
269 | There : LooseColumnMapping castTy xs (ident, ty) -> LooseColumnMapping castTy (x :: xs) (ident, ty)
272 | HasLooseMappings : {n : _}
273 | -> (0 castTy : PType -> Type -> Type)
274 | -> PostgresTable t =>
276 | -> (cols : Vect n (ColumnIdentifier, Type))
278 | HasLooseMappings castTy table cols = All (LooseColumnMapping castTy (columns table)) cols
285 | data ColumnMapping : (0 _ : PType -> Type -> Type)
286 | -> List (ColumnIdentifier, Exists PColType)
287 | -> (ColumnIdentifier, Type)
289 | HereNul : (ident : ColumnIdentifier) -> (ty : Type) -> castTy pt ty => ColumnMapping castTy ((ident, (Evidence pt (MkColType Nullable pt))) :: xs) (ident, Maybe ty)
290 | Here : (ident : ColumnIdentifier) -> (ty : Type) -> castTy pt ty => ColumnMapping castTy ((ident, (Evidence pt (MkColType NonNullable pt))) :: xs) (ident, ty)
291 | There : ColumnMapping castTy xs (ident, ty) -> ColumnMapping castTy (x :: xs) (ident, ty)
294 | HasMappings : {n : _}
295 | -> (0 castTy : PType -> Type -> Type)
296 | -> PostgresTable t =>
298 | -> (cols : Vect n (ColumnIdentifier, Type))
300 | HasMappings castTy table cols = All (ColumnMapping castTy (columns table)) cols
302 | toString : LooseColumnMapping PGCast table (colName, colType) -> colType -> String
303 | toString (HereNul name ty @{prf}) = \case Nothing => "null";
Just x => rawString $
toPostgres @{prf} x
304 | toString (HereNulLoose name ty @{prf}) = \case Nothing => "null";
Just x => rawString $
toPostgres @{prf} x
305 | toString (Here name ty @{prf}) = rawString . toPostgres @{prf}
306 | toString (HereLoose name ty @{prf}) = rawString . toPostgres @{prf}
307 | toString (There y) = toString y
312 | select : PostgresTable t =>
314 | -> (cols : Vect n (ColumnIdentifier, Type))
315 | -> (0 _ : HasMappings IdrCast table cols) =>
317 | select table cols =
318 | let tableStatement = show $
tableStatement table
319 | columnNames = joinBy "," $
show . fst <$> (toList cols)
320 | in "SELECT \{columnNames} FROM \{tableStatement}"
325 | -> (table : PersistedTable)
326 | -> (cols : Vect n ColumnIdentifier)
327 | -> {colTypes : Vect n Type}
328 | -> (values : HVect colTypes)
329 | -> HasLooseMappings PGCast table (zip cols colTypes) =>
331 | insert table cols vs @{mappings} =
332 | let tableIdentifier = show $
Id table.tableName
333 | columnNames = (strCons '(' (joinBy "," $
show . .name <$> (toList cols))) ++ ")"
334 | values = values table cols vs
335 | valueStrings = (strCons '(' (joinBy "," $
values)) ++ ")"
336 | in "INSERT INTO \{tableIdentifier} \{columnNames} VALUES \{valueStrings}"
339 | -> (table : PersistedTable)
340 | -> (cols : Vect l ColumnIdentifier)
341 | -> {colTypes : Vect l Type}
342 | -> (values : HVect colTypes)
343 | -> HasLooseMappings PGCast table (zip cols colTypes) =>
345 | values table [] [] = []
346 | values table (x :: xs) (v :: vs) @{m :: ms} = toString m v :: values table xs vs @{ms}
349 | data Join : t1 -> t2 -> Type where
350 | On : PostgresTable t => PostgresTable u => {t1 : t} -> {t2 : u} -> (c1 : ColumnIdentifier) -> (c2 : ColumnIdentifier) -> (0 _ : Elem c1 (Builtin.fst <$> (columns t1))) => (0 _ : Elem c2 (Builtin.fst <$> (columns t2))) => Join t1 t2
353 | HasOnMapping : ColumnIdentifier -> PostgresTable t => (table : t) -> Type
354 | HasOnMapping c table = Either (Elem c (fst <$> (columns table))) (AnonymousColumnIdentifier c, Elem (replaceSource (aliasOrName table) c) (fst <$> (columns table)))
357 | elemForOnMapping : PostgresTable t =>
359 | -> (c : ColumnIdentifier)
360 | -> HasOnMapping c table
361 | -> (
c' ** Elem c' (Builtin.fst <$> (columns table)))
362 | elemForOnMapping table c (Left m) = (
c ** m)
363 | elemForOnMapping table c (Right (_, m)) = (
replaceSource (aliasOrName table) c ** m)
372 | (==) : PostgresTable t =>
376 | -> (c1 : ColumnIdentifier)
377 | -> (c2 : ColumnIdentifier)
378 | -> {auto on1 : HasOnMapping c1 t1}
379 | -> {auto on2 : HasOnMapping c2 t2}
381 | (==) c1 c2 {on1} {on2} with (elemForOnMapping t1 c1 on1, elemForOnMapping t2 c2 on2)
382 | (==) c1 c2 {on1} {on2} | ((
c1' ** on1')
, (
c2' ** on2')
) = On c1' c2'
386 | column1 : Join t u -> ColumnIdentifier
387 | column1 (On c1 c2) = c1
390 | column2 : Join t u -> ColumnIdentifier
391 | column2 (On c1 c2) = c2
399 | innerJoin' : PostgresTable t => (table1 : t) -> PostgresTable u => (table2 : u) -> (on : Join table1 table2) -> RuntimeTable
400 | innerJoin' table1 table2 joinOn =
401 | let table1Statement = show $
tableStatement table1
402 | table2Statement = show $
tableStatement table2
403 | table1JoinName = show $
column1 joinOn
404 | table2JoinName = show $
column2 joinOn
405 | subquery = "\{table1Statement} JOIN \{table2Statement} ON \{table1JoinName} = \{table2JoinName}"
406 | table1Cols = columns table1
407 | table2Cols = columns table2
408 | in RT (Fragment subquery) (table1Cols ++ table2Cols)
416 | leftJoin' : PostgresTable t => (table1 : t) -> PostgresTable u => (table2 : u) -> (on : Join table1 table2) -> RuntimeTable
417 | leftJoin' table1 table2 joinOn =
418 | let table1Statement = show $
tableStatement table1
419 | table2Statement = show $
tableStatement table2
420 | table1JoinName = show $
column1 joinOn
421 | table2JoinName = show $
column2 joinOn
422 | subquery = "\{table1Statement} LEFT JOIN \{table2Statement} ON \{table1JoinName} = \{table2JoinName}"
423 | table1Cols = columns table1
424 | table2Cols = mapSnd (bimap (\t => t) makeNullable) <$> columns table2
426 | in RT (Fragment subquery) (table1Cols ++ table2Cols)
434 | rightJoin' : PostgresTable t => (table1 : t) -> PostgresTable u => (table2 : u) -> (on : Join table1 table2) -> RuntimeTable
435 | rightJoin' table1 table2 joinOn =
436 | let table1Statement = show $
tableStatement table1
437 | table2Statement = show $
tableStatement table2
438 | table1JoinName = show $
column1 joinOn
439 | table2JoinName = show $
column2 joinOn
440 | subquery = "\{table1Statement} LEFT JOIN \{table2Statement} ON \{table1JoinName} = \{table2JoinName}"
441 | table1Cols = mapSnd (bimap (\t => t) makeNullable) <$> columns table1
443 | table2Cols = columns table2
444 | in RT (Fragment subquery) (table1Cols ++ table2Cols)
447 | data JoinType = Inner | Left | Right
450 | data TablePair : table1 -> table2 -> Type where
451 | MkTP : JoinType -> PostgresTable t => PostgresTable u => (table1 : t) -> (table2 : u) -> TablePair table1 table2
457 | innerJoin : PostgresTable t => PostgresTable u => (table1 : t) -> (table2 : u) -> TablePair table1 table2
458 | innerJoin = MkTP Inner
464 | leftJoin : PostgresTable t => PostgresTable u => (table1 : t) -> (table2 : u) -> TablePair table1 table2
465 | leftJoin = MkTP Left
471 | rightJoin : PostgresTable t => PostgresTable u => (table1 : t) -> (table2 : u) -> TablePair table1 table2
472 | rightJoin = MkTP Right
481 | onColumns : TablePair table1 table2 -> Join table1 table2 -> RuntimeTable
482 | onColumns (MkTP Inner table1 table2) joinOn = innerJoin' table1 table2 joinOn
483 | onColumns (MkTP Left table1 table2) joinOn = leftJoin' table1 table2 joinOn
484 | onColumns (MkTP Right table1 table2) joinOn = rightJoin' table1 table2 joinOn
487 | infixl 10 `innerJoin`
, `leftJoin`
, `rightJoin`
, `onColumns`
489 | mappingCastable : {cs : _} -> ColumnMapping IdrCast cs (ident, ty) => Castable ty
490 | mappingCastable {cs = ((ident, Evidence pt (MkColType Nullable pt)) :: xs)} @{(HereNul ident x @{sc})} = CastMaybe @{IdrCastString {pt}}
491 | mappingCastable {cs = ((ident, Evidence pt (MkColType NonNullable pt)) :: xs)} @{(Here ident ty @{sc})} = Cast @{IdrCastString {pt}}
492 | mappingCastable {cs = (x :: xs)} @{(There y)} = mappingCastable {cs=xs} @{y}
495 | allCastable : PostgresTable t => {n : _} -> (table : t) -> {cols : Vect n _} -> HasMappings IdrCast table cols => All Castable (Builtin.snd <$> cols)
496 | allCastable @{_} table {cols = []} @{[]} = []
497 | allCastable @{_} table {cols = ((x, y) :: xs)} @{(m :: ms)} = (mappingCastable @{m}) :: allCastable table {cols=xs}