Skip to content
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

1601 default vals #1624

Open
wants to merge 63 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
dd3b533
Full app added
tobz619 Mar 25, 2023
515e7f7
ignoring .vscode config
tobz619 Mar 25, 2023
3c00bd3
Starting adding default tables for tests
tobz619 Mar 25, 2023
dbabf79
making default values: putting them into the tests
tobz619 Mar 25, 2023
be2b59a
minor edit
tobz619 Mar 25, 2023
73d29bc
removing unecessary files
tobz619 Mar 25, 2023
ec4d3d7
removing some silly mistakes in haddock
tobz619 Mar 25, 2023
310166d
PBoolean -> Real
tobz619 Mar 25, 2023
c0d4c48
Full app added
tobz619 Mar 25, 2023
b83259c
ignoring .vscode config
tobz619 Mar 25, 2023
2aaa334
Starting adding default tables for tests
tobz619 Mar 25, 2023
4fc4e34
making default values: putting them into the tests
tobz619 Mar 25, 2023
bb549a6
minor edit
tobz619 Mar 25, 2023
cfa88f1
removing unecessary files
tobz619 Mar 25, 2023
4e8a56e
removing some silly mistakes in haddock
tobz619 Mar 25, 2023
aca9eb1
PBoolean -> Real
tobz619 Mar 25, 2023
0c6dffd
Merge branch '1601-default-vals' of https://github.com/tobz619/ihp in…
tobz619 Mar 26, 2023
83b319b
Full app added
tobz619 Mar 25, 2023
ce10af1
ignoring .vscode config
tobz619 Mar 25, 2023
a93a0ab
Starting adding default tables for tests
tobz619 Mar 25, 2023
e8380fe
making default values: putting them into the tests
tobz619 Mar 25, 2023
5b2b577
minor edit
tobz619 Mar 25, 2023
44cdec3
removing unecessary files
tobz619 Mar 25, 2023
8495d52
removing some silly mistakes in haddock
tobz619 Mar 25, 2023
846ac8b
PBoolean -> Real
tobz619 Mar 25, 2023
66996e2
Full app added
tobz619 Mar 25, 2023
16665ab
ignoring .vscode config
tobz619 Mar 25, 2023
4360d6e
Starting adding default tables for tests
tobz619 Mar 25, 2023
f3649df
making default values: putting them into the tests
tobz619 Mar 25, 2023
fbfe5b3
minor edit
tobz619 Mar 25, 2023
7b4326e
removing unecessary files
tobz619 Mar 25, 2023
7a7fec2
Merge branch '1601-default-vals' of https://github.com/tobz619/ihp in…
tobz619 Mar 26, 2023
2e60d08
rebased + put defaults together
tobz619 Mar 26, 2023
4a3a49f
ParserSpec file complete + refactorings/helpers
tobz619 Mar 26, 2023
7d9ebb8
starting schema operations spec
tobz619 Mar 26, 2023
caf2067
changed one of the tests
tobz619 Mar 26, 2023
2f649ad
all tests should now pass
tobz619 Mar 26, 2023
69c8647
arrayTable change: cleaning up for the night
tobz619 Mar 26, 2023
fb3a655
some more changes, nearly complete, one test fail
tobz619 Mar 27, 2023
f844402
Moving the defaults and adding to ihp.nix
tobz619 Mar 27, 2023
23a5dc1
made final test pass
tobz619 Mar 27, 2023
1b927d6
all done :)
tobz619 Mar 28, 2023
7cc745a
search.js returned
tobz619 Mar 28, 2023
fbb6211
.gitignore in Guide restored
tobz619 Mar 28, 2023
eadfe00
Update IHP/IDE/Defaults/TableColumnDefaults.hs
tobz619 Mar 29, 2023
f59d3a0
search.js restored
tobz619 Mar 29, 2023
8fd27de
ihp reverted
tobz619 Mar 29, 2023
c8bd906
Readme.MD reverted
tobz619 Mar 29, 2023
eb64f2b
refactoring + removal of unnecessary names
tobz619 Mar 29, 2023
2f176a2
minor edits + removing pure in favour of list
tobz619 Mar 29, 2023
24829d4
Haddock
tobz619 Mar 29, 2023
72a7d4e
some minor, last minute errors
tobz619 Mar 29, 2023
f0a4814
Update IHP/IDE/Defaults/TableColumnDefaults.hs
tobz619 Mar 31, 2023
2f7bb68
Update Test/IDE/SchemaDesigner/SchemaOperationsSpec.hs
tobz619 Mar 31, 2023
63c578e
restored PGListener from Master
tobz619 Mar 31, 2023
b1793cf
Merge branch '1601-default-vals' of https://github.com/tobz619/ihp in…
tobz619 Mar 31, 2023
4a04c12
starting rewrite of tests - CompilerSpec complete
tobz619 Apr 5, 2023
7f7ab12
ParserSpec rewritten
tobz619 Apr 13, 2023
12a0179
Merge branch 'digitallyinduced:master' into 1601-default-vals
tobz619 Apr 13, 2023
cc4f359
Started Schema edits
tobz619 Apr 13, 2023
bfcb974
CompilerSpec Completed
tobz619 Apr 14, 2023
7b2ba87
fixed to fix broken tests
tobz619 Apr 14, 2023
26da4fa
final changes and test passes
tobz619 Apr 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
232 changes: 232 additions & 0 deletions IHP/IDE/Defaults/TableColumnDefaults.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
{-|
Module: Test.IDE.Defaults.TableColumnDefaults
tobz619 marked this conversation as resolved.
Show resolved Hide resolved
Copyright: (c) digitally induced GmbH, 2020
-}
module IHP.IDE.Defaults.TableColumnDefaults where

import Test.Hspec
import IHP.Prelude
import qualified IHP.IDE.CodeGen.ControllerGenerator as ControllerGenerator
import IHP.ViewPrelude (cs, plain)
import qualified Text.Megaparsec as Megaparsec
import IHP.IDE.CodeGen.Types
import IHP.IDE.SchemaDesigner.Types
import IHP.NameSupport
import IHP.IDE.SchemaDesigner.Types


{- | Takes a 'Text' value for the name and creates a default column where all values are empty lists,
'Nothing' or 'False'; and for 'ColumnType', the default is 'PUUID'.

Add a new field to the 'Column' type in the
"IHP.IDE.SchemaDesigner.Types" file and then
set its default value here

Defined as:

@
defColumn :: Column
defColumn = Column {
name = ""
, columnType = PUUID
, defaultValue = Nothing
, notNull = False
, isUnique = False
, generator = Nothing
}
@

If you want a different 'PostgresType' you will need to
specify with like so by either using a function:

@setColumnType pgt = defColumn {columnType = pgt}@
mpscholten marked this conversation as resolved.
Show resolved Hide resolved

Or

Just as part of where you're calling it:

@someDefaultColumnType = defColumn {columnType = PDate}@
-}
defColumn :: Column
defColumn =
Column
{ name = ""
, columnType = PUUID
, defaultValue = Nothing
, notNull = False
, isUnique = False
, generator = Nothing
}


{- | Creates an empty table with all values empty. @unlogged@ is set to 'False'.

Defined as such:

@
emptyTable :: CreateTable
emptyTable = CreateTable {
name = ""
, columns = []
, primaryKeyConstraint = PrimaryKeyConstraint []
, constraints = []
, unlogged = False
}
@

-}
emptyTable :: CreateTable
emptyTable = CreateTable
{ name = ""
, columns = []
, primaryKeyConstraint = PrimaryKeyConstraint []
, constraints = []
, unlogged = False
}

-- | Takes a name for our table and a list of column and inserts the list
-- into to our empty table.
defCreateTable :: Text -> [Column] -> CreateTable
defCreateTable tablename columns = emptyTable { name = tablename
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function doesn't seem useful to me, why can't we just write emptyTable { name = .. , columns = .. } directly?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's more for speed: it means you don't have to write the record parts over and over again especially if they're the only two values in emptyTable being changed and you could quickly be certain that the only values changed were the name and the columns contained. If other values needed changing, then it's up to the writer to use emptyTable {..} or (defCreateTable tablename columns){..}) -> both options are always available.

Since they were a recurring feature, it made sense to write them this way initially.

, columns = columns }




{- | Creates one default table with a singleton list of one 'setColumn'.

Uses both `defCreateTable` and `setColumn`.

@
defCreateTableWSetCol :: Text -- The name of the table
-> Text -- The name of the column
-> PostgresType -- The type of the column
-> CreateTable -- The returned table
@

-}
defCreateTableWSetCol :: Text -> Text -> PostgresType -> CreateTable
defCreateTableWSetCol tablename columnname pgt = defCreateTable tablename (pure $ setColumn columnname pgt)

{- | Same as its progenitor `defCreateTableWSetCol` except it uses `setColumnN`
-}
defCreateTableWSetColN :: Text -> Text -> PostgresType -> CreateTable
defCreateTableWSetColN tablename columnname pgt = defCreateTable tablename (pure $ setColumnN columnname pgt)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with those functions, I think it's more clear to write:

emptyTable { name = "users", columns = [ colUUID ] }

instead of

defCreateTableWSetCol "users" "id" ...

Copy link
Author

@tobz619 tobz619 Mar 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as before, for speed (not needing to write records) and composability (ability to easily use (.) and ($) to change values or combine them with other things). I do understand the concerns for clarity's sake though.

Since there were quite a few examples of where only certain values in certain fields are changed, if you know what the functions do, you can have a quick idea of what the table or column looks like or have a fast way of defining a more complex table as a base for another table by using base functions.

Again, the option the use the base tables and columns and record syntax is always there and would be functionally identical.

By using a specific function that encapsulates a pattern, I can quickly reason out the structure of the full table if I know what the function does UNLESS I modify it using ( ) and add more records with {..} or putting another function ahead of it which modifies the table further.

It really depends on much you want to represent specific patterns/structures of tests with functions. Also a lot of these functions can end up being helper functions much larger structures; but these will be made when needed I imagine.



{- | Takes the name of the table, the items you want inside the primaryKeyConstraint and a list of columns
to return a table where the primary key constraint is set.

__Example:__

@
let orderTrucksTable = defCreateTablePKID "orderTrucks" ["order_id","truck_id"] cols
where cols = map mkColumn ["order_id","truck_id"]
mkColumn x = (setColumnN x PBigserial)
@

>>> orderTrucksTable
CreateTable { name = "orderTrucks"
, columns = [ Column { name = "order_id",
columnType = PBigserial,
defaultValue = Nothing,
notNull = True,
isUnique = False,
generator = Nothing}
, Column { name = "truck_id",
columnType = PBigserial,
defaultValue = Nothing,
notNull = True,
isUnique = False,
generator = Nothing}
]
, primaryKeyConstraint = PrimaryKeyConstraint {primaryKeyColumnNames = ["order_id","truck_id"]}
, constraints = []
, unlogged = False}
-}
defCreateTablePKID :: Text -> [Text] -> [Column] -> CreateTable
defCreateTablePKID name items cols = (defCreateTable name cols) {primaryKeyConstraint = PrimaryKeyConstraint items}


{- | Allows you to set the name and columnType. Uses `defColumn` as its base

If other values need to be changed, this can be done using:
@(setColumn a b){..}@

__Example:__

>>> setColumn "user_id" PTrigger
Column {name = "user_id", columnType = PTrigger, defaultValue = Nothing, notNull = False, isUnique = False, generator = Nothing}

-}
setColumn :: Text -> PostgresType -> Column
setColumn name pgt = defColumn { name = name
, columnType = pgt
}

-- | A version of `setColumn` where @notNull = True@
setColumnN :: Text -> PostgresType -> Column
setColumnN n p = (setColumn n p) {notNull = True}

-- | Sets a column to have a default value. Would recommend using in conjunction with `setColumn`
setColumnDefaultVal :: Maybe Expression -> Column -> Column
setColumnDefaultVal expression column = column {defaultValue = expression}


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functions below this lines are IMO not a good idea. By having these very specific functions outside of the test suites we actually make tests harder to change, because you now need to look at two files to understands what's going on. So it would be better to undo the changes below and just make use of defTable and defColumn inside the tests.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So my thinking here was that if you had repeating units or tests, you can define them once, remember them and then use them as normal and so long as you know the definition of the table or column, you could:

  1. reuse it
  2. use it as a new base for a table/column

Perhaps it's not useful for whole tables but certainly default values/columns that may repeat and have complicated structures.

That said, I do see your point and there are several items that are superfluous; secondly, I'm sure if you needed to define repeatable units, you would define them as needed and perhaps on the test page like was done before.

Therefore, I will revert the tests to defintions of defColumn etc and leave space for IHP to define its own monomers as required.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was also thinking that the writer would check the definition of the test by ctrl+clicking on the name and opening up the references window -> but I guess not everyone uses vscode for IDE which is understandable.

{- | A recurring unit as found in many tests and files.

Defined as such:

>>> colUUID
Column {name = "id", columnType = PUUID, defaultValue = Just (CallExpression "uuid_generate_v4" []), notNull = True, isUnique = False, generator = Nothing}

-}
colUUID :: Column
colUUID = setColumnDefaultVal (Just (CallExpression "uuid_generate_v4" [])) $ setColumnN "id" PUUID
tobz619 marked this conversation as resolved.
Show resolved Hide resolved

{- | Give a column the text defined by 'text' and sets its 'columnType' to 'PText'. Uses `setColumnN`.

__Example:__

>>> colText "example"
Column {name = "example", columnType = PText, defaultValue = Nothing, notNull = True, isUnique = False, generator = Nothing}

-}
colText :: Text -> Column
colText text = setColumnN text PText
Comment on lines +195 to +196
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer to also just write emptyColumn { name, columnType = PText }

Copy link
Author

@tobz619 tobz619 Mar 31, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's written this way because setColumn and setColumnN use emptyColumn as precursors and edits them according to their patterns.

By partially applying setColumn/N with PText, one can make the assumption that any str called with colText will always return a column with the name of str, a columnType of PText and notNull value of True -> so for all cases where one needs to generate a column that will look like so, they can do so with one parameter and not mess with records and be reasonably confident that it will create the desired column.

Once again, the option to write with emptyColumn and records is also available.



{- | A recurring table that appears in many tests.

This is its current definition:

@
compilerSpecTable :: CreateTable
compilerSpecTable = defCreateTablePKID "users" ["id"] cols

where cols = [ colUUID
, colText "firstname"
, colText "lastname"
, colText "password_hash"
, colText "email"
, setColumnN "company_id" PUUID
, setColumn "picture_url" PText
, setColumnDefaultVal (Just (CallExpression "NOW" [])) $ setColumnN "created_at" PTimestampWithTimezone
]
@
-}
compilerSpecTable :: CreateTable
compilerSpecTable = defCreateTablePKID "users" ["id"] cols
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this is test related logic it shouldn't live here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we can move this into some test module instead


where cols = [ colUUID
, colText "firstname"
, colText "lastname"
, colText "password_hash"
, colText "email"
, setColumnN "company_id" PUUID
, setColumn "picture_url" PText
, setColumnDefaultVal (Just (CallExpression "NOW" [])) $ setColumnN "created_at" PTimestampWithTimezone
]



18 changes: 2 additions & 16 deletions IHP/IDE/SchemaDesigner/SchemaOperations.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,14 @@ import IHP.IDE.SchemaDesigner.Types
import Data.Maybe (fromJust)
import qualified Data.List as List
import qualified Data.Text as Text
import IHP.IDE.Defaults.TableColumnDefaults

-- | A Schema.sql basically is just a list of sql DDL statements
type Schema = [Statement]

-- | Creates a new tables with a 'id' columns as the primary key
addTable :: Text -> Schema -> Schema
addTable tableName list = list <> [StatementCreateTable CreateTable
{ name = tableName
, columns =
[Column
{ name = "id"
, columnType = PUUID
, defaultValue = Just (CallExpression "uuid_generate_v4" [])
, notNull = True
, isUnique = False
, generator = Nothing
}]
, primaryKeyConstraint = PrimaryKeyConstraint ["id"]
, constraints = []
, unlogged = False
}]

addTable tableName list = list <> [StatementCreateTable (defCreateTablePKID tableName ["id"] [colUUID])]

data AddColumnOptions = AddColumnOptions
{ tableName :: !Text
Expand Down
43 changes: 11 additions & 32 deletions IHP/PGListener.hs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ import qualified Data.Aeson as Aeson
import qualified IHP.Log as Log
import qualified Control.Exception as Exception
import qualified Control.Concurrent.Chan.Unagi as Queue
import qualified Control.Concurrent

-- TODO: How to deal with timeout of the connection?

Expand Down Expand Up @@ -232,38 +231,18 @@ notifyLoop listeningToVar listenToVar subscriptions = do
let inChan = get #inChan subscription
Queue.writeChan inChan notification

-- Initial delay (in microseconds)
tobz619 marked this conversation as resolved.
Show resolved Hide resolved
let initialDelay = 500 * 1000
-- Max delay (in microseconds)
let maxDelay = 60 * 1000 * 1000
-- This outer loop restarts the listeners if the database connection dies (e.g. due to a timeout)
let retryLoop delay isFirstError = do
result <- Exception.try innerLoop
case result of
Left (error :: SomeException) -> do
case fromException error of
Just (error :: AsyncCancelled) -> throw error
notification -> do
let ?context = ?modelContext -- Log onto the modelContext logger
if isFirstError then do
Log.info ("PGListener is going to restart, loop failed with exception: " <> (displayException error) <> ". Retrying immediately.")
retryLoop delay False -- Retry with no delay interval on first error, but will increase delay interval in subsequent retries
else do
let increasedDelay = delay * 2 -- Double current delay
let nextDelay = min increasedDelay maxDelay -- Picks whichever delay is lowest of increasedDelay * 2 or maxDelay
Log.info ("PGListener is going to restart, loop failed with exception: " <> (displayException error) <> ". Retrying in " <> cs (printTimeToNextRetry delay) <> ".")
Control.Concurrent.threadDelay delay -- Sleep for the current delay
retryLoop nextDelay False -- Retry with longer interval
Right _ ->
retryLoop initialDelay True -- If all went well, re-run with no sleeping and reset current delay to the initial value
retryLoop initialDelay True

printTimeToNextRetry :: Int -> Text
printTimeToNextRetry microseconds
| microseconds >= 1000000000 = show (microseconds `div` 1000000000) <> " min"
| microseconds >= 1000000 = show (microseconds `div` 1000000) <> " s"
| microseconds >= 1000 = show (microseconds `div` 1000) <> " ms"
| otherwise = show microseconds <> " µs"
forever do
result <- Exception.try innerLoop
case result of
Left (error :: SomeException) -> do
case fromException error of
Just (error :: AsyncCancelled) -> throw error
notification -> do
let ?context = ?modelContext -- Log onto the modelContext logger
Log.info ("PGListener is going to restart, loop failed with exception: " <> displayException error)
Right _ -> pure ()


listenToChannel :: PG.Connection -> Channel -> IO ()
listenToChannel databaseConnection channel = do
Expand Down
55 changes: 4 additions & 51 deletions Test/IDE/CodeGeneration/ControllerGenerator.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,61 +12,14 @@ import qualified Text.Megaparsec as Megaparsec
import IHP.IDE.CodeGen.Types
import IHP.IDE.SchemaDesigner.Types
import IHP.NameSupport
import IHP.IDE.Defaults.TableColumnDefaults

tests = do
describe "Controller Generator Tests:" do
let schema = [
StatementCreateTable CreateTable {
mpscholten marked this conversation as resolved.
Show resolved Hide resolved
name = "pages"
, columns = [
Column
{ name = "id"
, columnType = PUUID
, defaultValue = Just (CallExpression "uuid_generate_v4" [])
, notNull = True
, isUnique = False
, generator = Nothing
}
]
, primaryKeyConstraint = PrimaryKeyConstraint ["id"]
, constraints = []
, unlogged = False
},
StatementCreateTable CreateTable {
name = "people"
, columns = [
Column
{ name = "id"
, columnType = PUUID
, defaultValue = Just (CallExpression "uuid_generate_v4" [])
, notNull = True
, isUnique = False
, generator = Nothing
}
,
Column
{ name = "name"
, columnType = PText
, defaultValue = Nothing
, notNull = True
, isUnique = False
, generator = Nothing
}
,
Column
{ name = "email"
, columnType = PText
, defaultValue = Nothing
, notNull = True
, isUnique = False
, generator = Nothing
}
]
, primaryKeyConstraint = PrimaryKeyConstraint ["id"]
, constraints = []
, unlogged = False
}
]
StatementCreateTable (defCreateTablePKID "pages" ["id"] [colUUID])
, StatementCreateTable (defCreateTablePKID "people" ["id"] [colUUID, colText "name" , colText "email"])
]
it "should build a controller with name \"pages\"" do
let rawControllerName = "pages"
let controllerName = tableNameToControllerName rawControllerName
Expand Down
Loading