From 1d514e90e8063d731cef7ada8294a93fc8ea5ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rados=C5=82aw=20Wa=C5=9Bko?= Date: Mon, 5 Jun 2023 16:49:27 +0200 Subject: [PATCH] Split extensions into 2 files to avoid renaming. --- .../src/Extensions/Upload_Database_Table.enso | 90 +++++++++++++++++++ .../Extensions/Upload_Default_Helpers.enso | 35 ++++++++ ...Table.enso => Upload_In_Memory_Table.enso} | 41 ++------- .../Standard/Database/0.0.0-dev/src/Main.enso | 6 +- .../Types/SQLite_Type_Mapping_Spec.enso | 3 +- .../Table_Tests/src/Database/Upload_Spec.enso | 2 +- .../Table_Tests/src/In_Memory/Table_Spec.enso | 3 +- 7 files changed, 139 insertions(+), 41 deletions(-) create mode 100644 distribution/lib/Standard/Database/0.0.0-dev/src/Extensions/Upload_Database_Table.enso create mode 100644 distribution/lib/Standard/Database/0.0.0-dev/src/Extensions/Upload_Default_Helpers.enso rename distribution/lib/Standard/Database/0.0.0-dev/src/Extensions/{Upload_Table.enso => Upload_In_Memory_Table.enso} (78%) diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Extensions/Upload_Database_Table.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Extensions/Upload_Database_Table.enso new file mode 100644 index 0000000000000..e39c32cedfebe --- /dev/null +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Extensions/Upload_Database_Table.enso @@ -0,0 +1,90 @@ +from Standard.Base import all +import Standard.Base.Errors.Illegal_Argument.Illegal_Argument + +import Standard.Table.Internal.Widget_Helpers +from Standard.Table.Errors import all +from Standard.Table import Column_Selector + +import project.Connection.Connection.Connection +import project.Data.Table.Table +import project.Data.Update_Action.Update_Action +from project.Errors import all +from project.Internal.Result_Set import result_set_to_table +from project.Internal.Upload_Table import all + +## Creates a new database table from this table. + + Arguments: + - connection: the database connection to use. The table will be created in + the database and schema associated with this connection. + - table_name: the name of the table to create. If not provided, a random name + will be generated for temporary tables. If `temporary=False`, then a name + must be provided. + - primary_key: the names of the columns to use as the primary key. The first + column from the table is used by default. If it is set to `Nothing` or an + empty vector, no primary key will be created. + - temporary: if set to `True`, the table will be temporary, meaning that it + will be dropped once the `connection` is closed. Defaults to `False`. + - on_problems: the behavior to use when encountering non-fatal problems. + Defaults to reporting them as warning. + + ! Error Conditions + + - If a table with the given name already exists, then a + `Table_Already_Exists` error is raised. + - If a column type is not supported and is coerced to a similar supported + type, an `Inexact_Type_Coercion` problem is reported according to the + `on_problems` setting. + - If a column type is not supported and there is no replacement (e.g. + native Enso types), an `Unsupported_Type` error is raised. + - If the provided primary key columns are not present in the source table, + `Missing_Input_Columns` error is raised. + - If the selected primary key columns are not unique, a + `Non_Unique_Primary_Key` error is raised. + - An `SQL_Error` may be reported if there is a failure on the database + side. + + If an error has been raised, the table is not created (that may not always + apply to `SQL_Error`). +@primary_key Widget_Helpers.make_column_name_vector_selector +Table.select_into_database_table : Connection -> Text|Nothing -> Vector Text | Nothing -> Boolean -> Problem_Behavior -> Table ! Table_Already_Exists | Inexact_Type_Coercion | Missing_Input_Columns | Non_Unique_Primary_Key | SQL_Error | Illegal_Argument +Table.select_into_database_table self connection table_name=Nothing primary_key=[self.columns.first.name] temporary=False on_problems=Problem_Behavior.Report_Warning = Panic.recover SQL_Error <| + Panic.recover SQL_Error <| + connection.jdbc_connection.run_within_transaction <| + upload_database_table self connection table_name primary_key temporary on_problems + +## Updates the target table with the contents of this table. + + Arguments: + - connection: the database connection of the target table. + - table_name: the name of the table to update. + - update_action: specifies the update strategy - how to handle existing new + and missing rows. + - key_columns: the names of the columns to use identify correlate rows from + the source table with rows in the target table. This key is used to + determine if a row from the source table exists in the target or is a new + one. + - error_on_missing_columns: if set to `False` (the default), any columns + missing from the source table will be left unchanged or initialized with + the default value if inserting. If a missing column has no default value, + this will trigger a `SQL_Error`. If set to `True`, any columns missing from + the source will cause an error. + - on_problems: the behavior to use when encountering non-fatal problems. + + ! Error Conditions + + - If `key_columns` are not present in either the source or target tables, a + `Missing_Input_Columns` error is raised. + - If the target table does not exist, a `Table_Not_Found` error is raised. + - If `error_on_missing_columns` is set to `True` and a column is missing + from the source table, a `Missing_Input_Columns` error is raised. + - If the source table contains columns that are not present in the target + table, an `Unmatched_Columns` error is raised. + - If a column in the source table has a type that cannot be trivially + widened to the corresponding column in the target table, a + `Column_Type_Mismatch` error is raised. + + If any error was raised, the data in the target table is not modified. +Table.update_database_table : Connection -> Text -> Update_Action -> Vector Text-> Boolean -> Table ! Table_Not_Found | Unmatched_Columns | Missing_Input_Columns | Column_Type_Mismatch | SQL_Error | Illegal_Argument +Table.update_database_table connection (table_name : Text) (update_action : Update_Action = Update_Action.Update_Or_Insert) (key_columns : Vector = default_key_columns connection table_name) (error_on_missing_columns : Boolean = False) = + common_update_table self connection table_name update_action key_columns error_on_missing_columns diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Extensions/Upload_Default_Helpers.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Extensions/Upload_Default_Helpers.enso new file mode 100644 index 0000000000000..abb643c5f3b36 --- /dev/null +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Extensions/Upload_Default_Helpers.enso @@ -0,0 +1,35 @@ +from Standard.Base import all +import Standard.Base.Errors.Illegal_Argument.Illegal_Argument + +from Standard.Table import Column_Selector + +## PRIVATE +default_key_columns connection table_name = + keys = get_primary_key connection table_name + keys.catch Any _-> + Error.throw (Illegal_Argument.Error "Could not determine the primary key for table "+table_name+". Please provide it explicitly.") + +## PRIVATE + + This method may not work correctly with temporary tables, possibly resulting + in `SQL_Error` as such tables may not be found. + + ! Temporary Tables in SQLite + + The temporary tables in SQLite live in a `temp` database. There is a bug in + how JDBC retrieves primary keys - it only queries the `sqlite_schema` table + which contains schemas of only permanent tables. + + Ideally, we should provide a custom implementation for SQLite that will + UNION both `sqlite_schema` and `temp.sqlite_schema` tables to get results + for both temporary and permanent tables. + + TODO [RW] fix keys for SQLite temporary tables and test it +get_primary_key connection table_name = + connection.jdbc_connection.with_connection java_connection-> + rs = java_connection.getMetaData.getPrimaryKeys Nothing Nothing table_name + keys_table = result_set_to_table rs connection.dialect.make_column_fetcher_for_type + # The names of the columns are sometimes lowercase and sometimes uppercase, so we do a case insensitive select first. + selected = keys_table.select_columns [Column_Selector.By_Name "COLUMN_NAME", Column_Selector.By_Name "KEY_SEQ"] reorder=True + key_column_names = selected.order_by 1 . at 0 . to_vector + if key_column_names.is_empty then Nothing else key_column_names diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Extensions/Upload_Table.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Extensions/Upload_In_Memory_Table.enso similarity index 78% rename from distribution/lib/Standard/Database/0.0.0-dev/src/Extensions/Upload_Table.enso rename to distribution/lib/Standard/Database/0.0.0-dev/src/Extensions/Upload_In_Memory_Table.enso index ccf0569adbc23..1df063645057c 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Extensions/Upload_Table.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Extensions/Upload_In_Memory_Table.enso @@ -1,7 +1,7 @@ from Standard.Base import all import Standard.Base.Errors.Illegal_Argument.Illegal_Argument -import Standard.Table.Data.Table.Table as In_Memory_Table +import Standard.Table.Data.Table.Table import Standard.Table.Internal.Widget_Helpers from Standard.Table.Errors import all from Standard.Table import Column_Selector @@ -48,8 +48,8 @@ from project.Internal.Upload_Table import all If an error has been raised, the table is not created (that may not always apply to `SQL_Error`). @primary_key Widget_Helpers.make_column_name_vector_selector -In_Memory_Table.select_into_database_table : Connection -> Text|Nothing -> Vector Text | Nothing -> Boolean -> Problem_Behavior -> Database_Table ! Table_Already_Exists | Inexact_Type_Coercion | Missing_Input_Columns | Non_Unique_Primary_Key | SQL_Error | Illegal_Argument -In_Memory_Table.select_into_database_table self connection table_name=Nothing primary_key=[self.columns.first.name] temporary=False on_problems=Problem_Behavior.Report_Warning = +Table.select_into_database_table : Connection -> Text|Nothing -> Vector Text | Nothing -> Boolean -> Problem_Behavior -> Database_Table ! Table_Already_Exists | Inexact_Type_Coercion | Missing_Input_Columns | Non_Unique_Primary_Key | SQL_Error | Illegal_Argument +Table.select_into_database_table self connection table_name=Nothing primary_key=[self.columns.first.name] temporary=False on_problems=Problem_Behavior.Report_Warning = Panic.recover SQL_Error <| connection.jdbc_connection.run_within_transaction <| upload_in_memory_table self connection table_name primary_key temporary on_problems @@ -131,8 +131,8 @@ Database_Table.select_into_database_table self connection table_name=Nothing pri `Column_Type_Mismatch` error is raised. If any error was raised, the data in the target table is not modified. -In_Memory_Table.update_database_table : Connection -> Text -> Update_Action -> Vector Text-> Boolean -> Database_Table ! Table_Not_Found | Unmatched_Columns | Missing_Input_Columns | Column_Type_Mismatch | SQL_Error | Illegal_Argument -In_Memory_Table.update_database_table connection (table_name : Text) (update_action : Update_Action = Update_Action.Update_Or_Insert) (key_columns : Vector = default_key_columns connection table_name) (error_on_missing_columns : Boolean = False) = +Table.update_database_table : Connection -> Text -> Update_Action -> Vector Text-> Boolean -> Database_Table ! Table_Not_Found | Unmatched_Columns | Missing_Input_Columns | Column_Type_Mismatch | SQL_Error | Illegal_Argument +Table.update_database_table connection (table_name : Text) (update_action : Update_Action = Update_Action.Update_Or_Insert) (key_columns : Vector = default_key_columns connection table_name) (error_on_missing_columns : Boolean = False) = common_update_table self connection table_name update_action key_columns error_on_missing_columns ## Updates the target table with the contents of this table. @@ -170,34 +170,3 @@ In_Memory_Table.update_database_table connection (table_name : Text) (update_act Database_Table.update_database_table : Connection -> Text -> Update_Action -> Vector Text-> Boolean -> Database_Table ! Table_Not_Found | Unmatched_Columns | Missing_Input_Columns | Column_Type_Mismatch | SQL_Error | Illegal_Argument Database_Table.update_database_table connection (table_name : Text) (update_action : Update_Action = Update_Action.Update_Or_Insert) (key_columns : Vector = default_key_columns connection table_name) (error_on_missing_columns : Boolean = False) = common_update_table self connection table_name update_action key_columns error_on_missing_columns - -## PRIVATE -default_key_columns connection table_name = - keys = get_primary_key connection table_name - keys.catch Any _-> - Error.throw (Illegal_Argument.Error "Could not determine the primary key for table "+table_name+". Please provide it explicitly.") - -## PRIVATE - - This method may not work correctly with temporary tables, possibly resulting - in `SQL_Error` as such tables may not be found. - - ! Temporary Tables in SQLite - - The temporary tables in SQLite live in a `temp` database. There is a bug in - how JDBC retrieves primary keys - it only queries the `sqlite_schema` table - which contains schemas of only permanent tables. - - Ideally, we should provide a custom implementation for SQLite that will - UNION both `sqlite_schema` and `temp.sqlite_schema` tables to get results - for both temporary and permanent tables. - - TODO [RW] fix keys for SQLite temporary tables and test it -get_primary_key connection table_name = - connection.jdbc_connection.with_connection java_connection-> - rs = java_connection.getMetaData.getPrimaryKeys Nothing Nothing table_name - keys_table = result_set_to_table rs connection.dialect.make_column_fetcher_for_type - # The names of the columns are sometimes lowercase and sometimes uppercase, so we do a case insensitive select first. - selected = keys_table.select_columns [Column_Selector.By_Name "COLUMN_NAME", Column_Selector.By_Name "KEY_SEQ"] reorder=True - key_column_names = selected.order_by 1 . at 0 . to_vector - if key_column_names.is_empty then Nothing else key_column_names diff --git a/distribution/lib/Standard/Database/0.0.0-dev/src/Main.enso b/distribution/lib/Standard/Database/0.0.0-dev/src/Main.enso index 66e8fec763ba7..68426756126c6 100644 --- a/distribution/lib/Standard/Database/0.0.0-dev/src/Main.enso +++ b/distribution/lib/Standard/Database/0.0.0-dev/src/Main.enso @@ -9,7 +9,8 @@ import project.Connection.SQLite_Details.In_Memory import project.Connection.SQLite_Format.SQLite_Format import project.Connection.SSL_Mode.SSL_Mode import project.Data.SQL_Query.SQL_Query -import project.Extensions.Upload_Table +import project.Extensions.Upload_Database_Table +import project.Extensions.Upload_In_Memory_Table from project.Connection.Postgres_Details.Postgres_Details import Postgres from project.Connection.SQLite_Details.SQLite_Details import SQLite @@ -25,7 +26,8 @@ export project.Connection.SQLite_Details.In_Memory export project.Connection.SQLite_Format.SQLite_Format export project.Connection.SSL_Mode.SSL_Mode export project.Data.SQL_Query.SQL_Query -export project.Extensions.Upload_Table +export project.Extensions.Upload_Database_Table +export project.Extensions.Upload_In_Memory_Table from project.Connection.Postgres_Details.Postgres_Details export Postgres from project.Connection.SQLite_Details.SQLite_Details export SQLite diff --git a/test/Table_Tests/src/Database/Types/SQLite_Type_Mapping_Spec.enso b/test/Table_Tests/src/Database/Types/SQLite_Type_Mapping_Spec.enso index 03e0467a03e40..4ff60f59c6a2e 100644 --- a/test/Table_Tests/src/Database/Types/SQLite_Type_Mapping_Spec.enso +++ b/test/Table_Tests/src/Database/Types/SQLite_Type_Mapping_Spec.enso @@ -5,7 +5,8 @@ from Standard.Table import Aggregate_Column, Value_Type, Table from Standard.Table.Errors import Invalid_Value_Type, Inexact_Type_Coercion import Standard.Database.Data.Dialect -import Standard.Database.Extensions.Upload_Table +import Standard.Database.Extensions.Upload_Database_Table +import Standard.Database.Extensions.Upload_In_Memory_Table import Standard.Database.Internal.SQLite.SQLite_Type_Mapping from Standard.Database import Database, SQLite, In_Memory, SQL_Query from Standard.Database.Errors import Unsupported_Database_Operation diff --git a/test/Table_Tests/src/Database/Upload_Spec.enso b/test/Table_Tests/src/Database/Upload_Spec.enso index b21101f78293c..2dfcc80b54fb6 100644 --- a/test/Table_Tests/src/Database/Upload_Spec.enso +++ b/test/Table_Tests/src/Database/Upload_Spec.enso @@ -7,7 +7,7 @@ from Standard.Table.Errors import Missing_Input_Columns from Standard.Database import all from Standard.Database.Errors import all from Standard.Database.Internal.Result_Set import result_set_to_table -from Standard.Database.Extensions.Upload_Table import get_primary_key +from Standard.Database.Extensions.Upload_Default_Helpers import get_primary_key from Standard.Test import Test, Test_Suite, Problems import Standard.Test.Extensions diff --git a/test/Table_Tests/src/In_Memory/Table_Spec.enso b/test/Table_Tests/src/In_Memory/Table_Spec.enso index af5efd2d6c150..6b85e554cdf5c 100644 --- a/test/Table_Tests/src/In_Memory/Table_Spec.enso +++ b/test/Table_Tests/src/In_Memory/Table_Spec.enso @@ -12,7 +12,8 @@ from Standard.Table.Errors import Invalid_Output_Column_Names, Duplicate_Output_ import Standard.Visualization -import Standard.Database.Extensions.Upload_Table +import Standard.Database.Extensions.Upload_Database_Table +import Standard.Database.Extensions.Upload_In_Memory_Table from Standard.Database import Database, SQLite, In_Memory from Standard.Test import Test, Test_Suite, Problems