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

Snowflake Connectivity #9435

Merged
merged 25 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d4f19cb
Initial connection to Snowflake via an account, username and password.
jdunkerley Mar 14, 2024
7eae8c8
Fix databases and schemas in Snowflake.
jdunkerley Mar 15, 2024
c20618c
Add warehouse.
jdunkerley Mar 15, 2024
e6d1075
Add ability to set warehouse and pass at connect.
jdunkerley Mar 15, 2024
fb8b8ee
Fix for NPE in license review
radeusgd Mar 15, 2024
f6b1f11
scalafmt
radeusgd Mar 15, 2024
e1c3602
Separate Snowflake from Database.
jdunkerley Mar 15, 2024
7f4b38b
Scala fmt.
jdunkerley Mar 15, 2024
957a308
Legal Review
jdunkerley Mar 15, 2024
08c8148
Avoid using ARROW for snowflake.
jdunkerley Mar 15, 2024
ac999c8
Tidy up Entity_Naming_Properties.
jdunkerley Mar 15, 2024
a0c2b77
Fix for separating Entity_Namimg_Properties.
jdunkerley Mar 18, 2024
df5a348
Allow some tweaking of Postgres dialect to allow snowflake to use as …
jdunkerley Mar 18, 2024
4752e1c
Working on reading Date, Time and Date Times.
jdunkerley Mar 18, 2024
4ff2bc5
Changelog.
jdunkerley Mar 18, 2024
309d2ee
Java format.
jdunkerley Mar 18, 2024
e4e0160
Make Snowflake Time and TimeStamp stuff work.
jdunkerley Mar 19, 2024
637b456
Make Snowflake Time and TimeStamp stuff work.
jdunkerley Mar 19, 2024
36e36eb
fix
radeusgd Mar 19, 2024
cfc851c
Update distribution/lib/Standard/Database/0.0.0-dev/src/Connection/Co…
jdunkerley Mar 19, 2024
7d0363e
PR comments.
jdunkerley Mar 19, 2024
03d2d2a
Last refactor for PR.
jdunkerley Mar 19, 2024
33cac99
Merge branch 'develop' into wip/jd/table-fixes
jdunkerley Mar 19, 2024
d753e23
Fix.
jdunkerley Mar 19, 2024
76f95ba
Merge branch 'develop' into wip/jd/table-fixes
mergify[bot] Mar 20, 2024
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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,8 @@
- [Implemented constructors, comparisons, and arithmetic for a `Decimal`
type.][9272]
- [Allow `Table.replace` to take mutiple target columns.][9406]
- [Initial Snowflake Support - ability to read and query, not a completed
dialect yet.][9435]

[debug-shortcuts]:
https://github.com/enso-org/enso/blob/develop/app/gui/docs/product/shortcuts.md#debug
Expand Down Expand Up @@ -925,6 +927,7 @@
[9382]: https://github.com/enso-org/enso/pull/9382
[9343]: https://github.com/enso-org/enso/pull/9343
[9406]: https://github.com/enso-org/enso/pull/9406
[9435]: https://github.com/enso-org/enso/pull/9435

#### Enso Compiler

Expand Down
42 changes: 41 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,8 @@ GatherLicenses.distributions := Seq(
makeStdLibDistribution("Table", Distribution.sbtProjects(`std-table`)),
makeStdLibDistribution("Database", Distribution.sbtProjects(`std-database`)),
makeStdLibDistribution("Image", Distribution.sbtProjects(`std-image`)),
makeStdLibDistribution("AWS", Distribution.sbtProjects(`std-aws`))
makeStdLibDistribution("AWS", Distribution.sbtProjects(`std-aws`)),
makeStdLibDistribution("Snowflake", Distribution.sbtProjects(`std-snowflake`))
)

GatherLicenses.licenseConfigurations := Set("compile")
Expand Down Expand Up @@ -330,6 +331,7 @@ lazy val enso = (project in file("."))
`std-image`,
`std-table`,
`std-aws`,
`std-snowflake`,
`http-test-helper`,
`enso-test-java-helpers`,
`exploratory-benchmark-java-helpers`,
Expand Down Expand Up @@ -512,6 +514,7 @@ val netbeansApiVersion = "RELEASE180"
val fansiVersion = "0.4.0"
val httpComponentsVersion = "4.4.1"
val apacheArrowVersion = "14.0.1"
val snowflakeJDBCVersion = "3.15.0"

// ============================================================================
// === Utility methods =====================================================
Expand Down Expand Up @@ -1650,6 +1653,7 @@ lazy val runtime = (project in file("engine/runtime"))
.dependsOn(`std-google-api` / Compile / packageBin)
.dependsOn(`std-table` / Compile / packageBin)
.dependsOn(`std-aws` / Compile / packageBin)
.dependsOn(`std-snowflake` / Compile / packageBin)
.value
)
.dependsOn(`common-polyglot-core-utils`)
Expand Down Expand Up @@ -2704,6 +2708,8 @@ val `database-polyglot-root` =
stdLibComponentRoot("Database") / "polyglot" / "java"
val `std-aws-polyglot-root` =
stdLibComponentRoot("AWS") / "polyglot" / "java"
val `std-snowflake-polyglot-root` =
stdLibComponentRoot("Snowflake") / "polyglot" / "java"

lazy val `std-base` = project
.in(file("std-bits") / "base")
Expand Down Expand Up @@ -2978,6 +2984,36 @@ lazy val `std-aws` = project
.dependsOn(`std-table` % "provided")
.dependsOn(`std-database` % "provided")

lazy val `std-snowflake` = project
.in(file("std-bits") / "snowflake")
.settings(
frgaalJavaCompilerSetting,
autoScalaLibrary := false,
Compile / compile / compileInputs := (Compile / compile / compileInputs)
.dependsOn(SPIHelpers.ensureSPIConsistency)
.value,
Compile / packageBin / artifactPath :=
`std-snowflake-polyglot-root` / "std-snowflake.jar",
libraryDependencies ++= Seq(
"org.netbeans.api" % "org-openide-util-lookup" % netbeansApiVersion % "provided",
"net.snowflake" % "snowflake-jdbc" % snowflakeJDBCVersion
),
Compile / packageBin := Def.task {
val result = (Compile / packageBin).value
val _ = StdBits
.copyDependencies(
`std-snowflake-polyglot-root`,
Seq("std-snowflake.jar"),
ignoreScalaLibrary = true
)
.value
result
}.value
)
.dependsOn(`std-base` % "provided")
.dependsOn(`std-table` % "provided")
.dependsOn(`std-database` % "provided")

/* Note [Native Image Workaround for GraalVM 20.2]
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* In GraalVM 20.2 the Native Image build of even simple Scala programs has
Expand Down Expand Up @@ -3123,6 +3159,7 @@ val stdBitsProjects =
"Database",
"Google_Api",
"Image",
"Snowflake",
"Table"
) ++ allStdBitsSuffix
val allStdBits: Parser[String] =
Expand Down Expand Up @@ -3191,6 +3228,8 @@ pkgStdLibInternal := Def.inputTask {
(`benchmark-java-helpers` / Compile / packageBin).value
case "AWS" =>
(`std-aws` / Compile / packageBin).value
case "Snowflake" =>
(`std-snowflake` / Compile / packageBin).value
case _ if buildAllCmd =>
(`std-base` / Compile / packageBin).value
(`enso-test-java-helpers` / Compile / packageBin).value
Expand All @@ -3201,6 +3240,7 @@ pkgStdLibInternal := Def.inputTask {
(`std-image` / Compile / packageBin).value
(`std-google-api` / Compile / packageBin).value
(`std-aws` / Compile / packageBin).value
(`std-snowflake` / Compile / packageBin).value
case _ =>
}
val libs =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,15 +96,6 @@ type Redshift_Dialect
get_type_mapping : SQL_Type_Mapping
get_type_mapping self = Postgres_Type_Mapping

## PRIVATE
Creates a `Column_Fetcher` used to fetch data from a result set and build
an in-memory column from it, based on the given column type.
make_column_fetcher_for_type : SQL_Type -> Column_Fetcher
make_column_fetcher_for_type self sql_type =
type_mapping = self.get_type_mapping
value_type = type_mapping.sql_type_to_value_type sql_type
Column_Fetcher_Module.default_fetcher_for_value_type value_type

## PRIVATE
get_statement_setter : Statement_Setter
get_statement_setter self = Postgres_Dialect.postgres_statement_setter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,10 @@ import Standard.Database.Connection.Connection.Connection
import Standard.Database.Connection.Connection_Options.Connection_Options
import Standard.Database.Connection.Credentials.Credentials
import Standard.Database.Connection.SSL_Mode.SSL_Mode
import Standard.Database.Internal.Connection.Entity_Naming_Properties.Entity_Naming_Properties
import Standard.Database.Internal.JDBC_Connection
import Standard.Database.Internal.Postgres.Pgpass
import Standard.Database.Internal.Postgres.Postgres_Entity_Naming_Properties
from Standard.Database.Internal.Postgres.Postgres_Connection import get_encoding_name, parse_postgres_encoding

import project.AWS_Credential.AWS_Credential
import project.Database.Redshift.Internal.Redshift_Dialect
Expand Down Expand Up @@ -45,7 +46,8 @@ type Redshift_Details
jdbc_connection = JDBC_Connection.create self.jdbc_url properties

# TODO [RW] can we inherit these from postgres?
entity_naming_properties = Postgres_Entity_Naming_Properties.new jdbc_connection
encoding = parse_postgres_encoding (get_encoding_name jdbc_connection)
entity_naming_properties = Entity_Naming_Properties.from_jdbc_connection jdbc_connection encoding is_case_sensitive=True

Connection.new jdbc_connection Redshift_Dialect.redshift entity_naming_properties

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,6 @@ type Connection
be obtained using the `table_types` method. Defaults to a set of most
commonly used table types, ignoring internal system tables or indices.
- all_fields: Return all the fields in the metadata table.
@types make_table_types_selector
@schema make_schema_selector
tables : Text -> Text -> Text -> Vector Text | Text | Nothing -> Boolean -> Table
tables self name_like=Nothing database=self.database schema=Nothing types=self.dialect.default_table_types all_fields=False =
self.get_tables_advanced name_like=name_like database=database schema=schema types=types all_fields=all_fields include_hidden=False
Expand All @@ -172,7 +170,7 @@ type Connection
name_map = Map.from_vector [["TABLE_CAT", "Database"], ["TABLE_SCHEM", "Schema"], ["TABLE_NAME", "Name"], ["TABLE_TYPE", "Type"], ["REMARKS", "Description"], ["TYPE_CAT", "Type Database"], ["TYPE_SCHEM", "Type Schema"], ["TYPE_NAME", "Type Name"]]
result = self.jdbc_connection.with_metadata metadata->
table = Managed_Resource.bracket (metadata.getTables database schema name_like types_vector) .close result_set->
result_set_to_table result_set self.dialect.make_column_fetcher_for_type
result_set_to_table result_set self.dialect.get_type_mapping.make_column_fetcher
renamed = table.rename_columns name_map
if all_fields then renamed else
renamed.select_columns ["Database", "Schema", "Name", "Type", "Description"]
Expand Down Expand Up @@ -334,7 +332,7 @@ type Connection
self.jdbc_connection.with_prepared_statement statement statement_setter stmt->
rs = stmt.executeQuery
SQL_Warning_Helper.process_warnings stmt <|
result_set_to_table rs self.dialect.make_column_fetcher_for_type type_overrides last_row_only
result_set_to_table rs self.dialect.get_type_mapping.make_column_fetcher type_overrides last_row_only

## PRIVATE
Given a prepared statement, gets the column names and types for the
Expand All @@ -344,6 +342,14 @@ type Connection
needs_execute_query = self.dialect.needs_execute_query_for_type_inference
self.jdbc_connection.raw_fetch_columns statement needs_execute_query statement_setter

## PRIVATE
Internal helper method to run a query and read a single column from the
result as a Vector.
read_single_column : Text -> Text -> Vector Text
read_single_column self query column_name = self.jdbc_connection.with_connection connection->
result_set = connection.createStatement.executeQuery query
read_column result_set column_name

## PRIVATE
ADVANCED

Expand Down Expand Up @@ -444,10 +450,18 @@ make_table_types_selector connection =
Single_Choice values=(connection.table_types.map t-> Option t t.pretty)

## PRIVATE
make_schema_selector : Connection -> Widget
make_schema_selector connection =
make_database_selector : Connection -> Boolean -> Widget
make_database_selector connection include_any:Boolean=False =
databases_without_nothing = connection.databases.filter Filter_Condition.Not_Nothing
any_entry = if include_any then [Option "any database" "'*'"] else []
Single_Choice values=(databases_without_nothing.map t-> Option t t.pretty)+any_entry

## PRIVATE
make_schema_selector : Connection -> Boolean -> Widget
make_schema_selector connection include_any:Boolean=False =
schemas_without_nothing = connection.schemas.filter Filter_Condition.Not_Nothing
Single_Choice values=(schemas_without_nothing.map t-> Option t t.pretty)+[Option "any schema" "Nothing"]
any_entry = if include_any then [Option "any schema" "'*'"] else []
Single_Choice values=(schemas_without_nothing.map t-> Option t t.pretty)+any_entry

## PRIVATE
make_table_name_selector : Connection -> Widget
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,14 +85,6 @@ type Dialect
get_type_mapping self =
Unimplemented.throw "This is an interface only."

## PRIVATE
Creates a `Column_Fetcher` used to fetch data from a result set and build
an in-memory column from it, based on the given column type.
make_column_fetcher_for_type : SQL_Type -> Column_Fetcher
make_column_fetcher_for_type self sql_type =
_ = sql_type
Unimplemented.throw "This is an interface only."

## PRIVATE
Returns a helper object that handles the logic of setting values in a
prepared statement.
Expand Down Expand Up @@ -264,7 +256,7 @@ default_fetch_types_query dialect expression context =
default_fetch_primary_key connection table_name =
connection.jdbc_connection.with_metadata metadata->
rs = metadata.getPrimaryKeys Nothing Nothing table_name
keys_table = result_set_to_table rs connection.dialect.make_column_fetcher_for_type
keys_table = result_set_to_table rs connection.dialect.get_type_mapping.make_column_fetcher
# 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_NAME", "KEY_SEQ"] case_sensitivity=Case_Sensitivity.Insensitive reorder=True
key_column_names = selected.order_by 1 . at 0 . to_vector
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import Standard.Table.Internal.Java_Exports
from project.Errors import Unsupported_Database_Operation

polyglot java import java.sql.ResultSet
polyglot java import java.time.LocalDate as Java_Local_Date
polyglot java import java.time.LocalTime as Java_Local_Time

polyglot java import org.enso.database.JDBCUtils
polyglot java import org.enso.table.problems.ProblemAggregator
Expand Down Expand Up @@ -132,19 +130,15 @@ fallback_fetcher =

## PRIVATE
date_fetcher =
fetch_value rs i =
time = rs.getObject i Java_Local_Date.class
if rs.wasNull then Nothing else time
fetch_value rs i = JDBCUtils.getLocalDate rs i
make_builder initial_size _ =
java_builder = Java_Exports.make_date_builder initial_size
make_builder_from_java_object_builder java_builder
Column_Fetcher.Value fetch_value make_builder

## PRIVATE
time_fetcher =
fetch_value rs i =
time = rs.getObject i Java_Local_Time.class
if rs.wasNull then Nothing else time
fetch_value rs i = JDBCUtils.getLocalTime rs i
make_builder initial_size _ =
java_builder = Java_Exports.make_time_of_day_builder initial_size
make_builder_from_java_object_builder java_builder
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
from Standard.Base import all
import Standard.Base.Errors.Illegal_Argument.Illegal_Argument
import Standard.Base.Errors.Illegal_State.Illegal_State

import project.Internal.Common.Encoding_Limited_Naming_Properties.Encoding_Limited_Naming_Properties
import project.Internal.JDBC_Connection.JDBC_Connection
from project.Internal.JDBC_Connection import get_pragma_value

## PRIVATE
type Entity_Naming_Properties
Expand All @@ -14,3 +19,18 @@ type Entity_Naming_Properties
- for_generated_column_names: properties of naming generated columns.
Should implement the `Naming_Properties` interface.
Value for_table_names for_column_names for_generated_column_names

## PRIVATE
from_jdbc_connection : JDBC_Connection -> Encoding -> Boolean -> Entity_Naming_Properties
from_jdbc_connection jdbc_connection encoding:Encoding=Encoding.utf_8 is_case_sensitive:Boolean=True =
jdbc_connection.with_metadata metadata->
table_limit = metadata.getMaxTableNameLength
if table_limit == 0 then
Panic.throw (Illegal_State.Error "Unexpected: The database server does not report the maximum table name length.")
column_limit = metadata.getMaxColumnNameLength
if column_limit == 0 then
Panic.throw (Illegal_State.Error "Unexpected: The database server does not report the maximum column name length.")

table_properties = Encoding_Limited_Naming_Properties.Instance encoding table_limit is_case_sensitive=is_case_sensitive
column_properties = Encoding_Limited_Naming_Properties.Instance encoding column_limit is_case_sensitive=is_case_sensitive
Entity_Naming_Properties.Value for_table_names=table_properties for_column_names=column_properties for_generated_column_names=column_properties
Original file line number Diff line number Diff line change
Expand Up @@ -321,3 +321,13 @@ log_sql_if_enabled jdbc_connection ~query_text =
log_line = timestamp + " [" + db_id + "] " + query_text + '\n'
Context.Output.with_enabled <|
log_line.write path on_existing_file=Existing_File_Behavior.Append

## PRIVATE
get_pragma_value : JDBC_Connection -> Text -> Any
get_pragma_value jdbc_connection sql =
table = jdbc_connection.raw_read_statement sql
if table.row_count != 1 then
Panic.throw (Illegal_State.Error "Unexpected amount of results to internal query: "+sql+"; expected 1 but got "+table.row_count.to_text+" rows.")
if table.columns.length != 1 then
Panic.throw (Illegal_State.Error "Unexpected amount of columns to internal query: "+sql+"; expected 1 but got "+table.columns.length.to_text+" columns.")
table.at 0 . at 0
Loading
Loading