etl-integrations 0.1.81

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +34 -0
  4. data/.ruby-version +1 -0
  5. data/.vscode/settings.json +5 -0
  6. data/CHANGELOG.md +38 -0
  7. data/CODE_OF_CONDUCT.md +84 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +105 -0
  10. data/Rakefile +12 -0
  11. data/lib/multiwoven/integrations/config.rb +13 -0
  12. data/lib/multiwoven/integrations/core/base_connector.rb +70 -0
  13. data/lib/multiwoven/integrations/core/constants.rb +46 -0
  14. data/lib/multiwoven/integrations/core/destination_connector.rb +14 -0
  15. data/lib/multiwoven/integrations/core/fullrefresher.rb +19 -0
  16. data/lib/multiwoven/integrations/core/http_client.rb +34 -0
  17. data/lib/multiwoven/integrations/core/query_builder.rb +27 -0
  18. data/lib/multiwoven/integrations/core/rate_limiter.rb +19 -0
  19. data/lib/multiwoven/integrations/core/source_connector.rb +38 -0
  20. data/lib/multiwoven/integrations/core/utils.rb +104 -0
  21. data/lib/multiwoven/integrations/destination/airtable/client.rb +153 -0
  22. data/lib/multiwoven/integrations/destination/airtable/config/catalog.json +6 -0
  23. data/lib/multiwoven/integrations/destination/airtable/config/meta.json +15 -0
  24. data/lib/multiwoven/integrations/destination/airtable/config/spec.json +22 -0
  25. data/lib/multiwoven/integrations/destination/airtable/icon.svg +6 -0
  26. data/lib/multiwoven/integrations/destination/airtable/schema_helper.rb +141 -0
  27. data/lib/multiwoven/integrations/destination/facebook_custom_audience/client.rb +124 -0
  28. data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/catalog.json +42 -0
  29. data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/meta.json +15 -0
  30. data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/spec.json +27 -0
  31. data/lib/multiwoven/integrations/destination/facebook_custom_audience/icon.svg +23 -0
  32. data/lib/multiwoven/integrations/destination/google_sheets/client.rb +231 -0
  33. data/lib/multiwoven/integrations/destination/google_sheets/config/catalog.json +6 -0
  34. data/lib/multiwoven/integrations/destination/google_sheets/config/meta.json +15 -0
  35. data/lib/multiwoven/integrations/destination/google_sheets/config/spec.json +74 -0
  36. data/lib/multiwoven/integrations/destination/google_sheets/icon.svg +1 -0
  37. data/lib/multiwoven/integrations/destination/hubspot/client.rb +107 -0
  38. data/lib/multiwoven/integrations/destination/hubspot/config/catalog.json +351 -0
  39. data/lib/multiwoven/integrations/destination/hubspot/config/meta.json +15 -0
  40. data/lib/multiwoven/integrations/destination/hubspot/config/spec.json +17 -0
  41. data/lib/multiwoven/integrations/destination/hubspot/icon.svg +5 -0
  42. data/lib/multiwoven/integrations/destination/klaviyo/client.rb +116 -0
  43. data/lib/multiwoven/integrations/destination/klaviyo/config/catalog.json +103 -0
  44. data/lib/multiwoven/integrations/destination/klaviyo/config/meta.json +15 -0
  45. data/lib/multiwoven/integrations/destination/klaviyo/config/spec.json +22 -0
  46. data/lib/multiwoven/integrations/destination/klaviyo/icon.svg +6 -0
  47. data/lib/multiwoven/integrations/destination/postgresql/client.rb +123 -0
  48. data/lib/multiwoven/integrations/destination/postgresql/config/meta.json +15 -0
  49. data/lib/multiwoven/integrations/destination/postgresql/config/spec.json +68 -0
  50. data/lib/multiwoven/integrations/destination/postgresql/icon.svg +20 -0
  51. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/client.rb +114 -0
  52. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/config/catalog.json +6 -0
  53. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/config/meta.json +16 -0
  54. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/config/spec.json +49 -0
  55. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/icon.svg +16 -0
  56. data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/schema_helper.rb +132 -0
  57. data/lib/multiwoven/integrations/destination/salesforce_crm/client.rb +117 -0
  58. data/lib/multiwoven/integrations/destination/salesforce_crm/config/catalog.json +320 -0
  59. data/lib/multiwoven/integrations/destination/salesforce_crm/config/meta.json +15 -0
  60. data/lib/multiwoven/integrations/destination/salesforce_crm/config/spec.json +43 -0
  61. data/lib/multiwoven/integrations/destination/salesforce_crm/icon.svg +16 -0
  62. data/lib/multiwoven/integrations/destination/sftp/client.rb +133 -0
  63. data/lib/multiwoven/integrations/destination/sftp/config/catalog.json +16 -0
  64. data/lib/multiwoven/integrations/destination/sftp/config/meta.json +16 -0
  65. data/lib/multiwoven/integrations/destination/sftp/config/spec.json +50 -0
  66. data/lib/multiwoven/integrations/destination/sftp/icon.svg +1 -0
  67. data/lib/multiwoven/integrations/destination/slack/client.rb +114 -0
  68. data/lib/multiwoven/integrations/destination/slack/config/catalog.json +22 -0
  69. data/lib/multiwoven/integrations/destination/slack/config/meta.json +15 -0
  70. data/lib/multiwoven/integrations/destination/slack/config/spec.json +22 -0
  71. data/lib/multiwoven/integrations/destination/slack/icon.svg +26 -0
  72. data/lib/multiwoven/integrations/destination/stripe/client.rb +82 -0
  73. data/lib/multiwoven/integrations/destination/stripe/config/catalog.json +128 -0
  74. data/lib/multiwoven/integrations/destination/stripe/config/meta.json +15 -0
  75. data/lib/multiwoven/integrations/destination/stripe/config/spec.json +17 -0
  76. data/lib/multiwoven/integrations/destination/stripe/icon.svg +10 -0
  77. data/lib/multiwoven/integrations/destination/tally/client.rb +151 -0
  78. data/lib/multiwoven/integrations/destination/tally/config/catalog.json +62 -0
  79. data/lib/multiwoven/integrations/destination/tally/config/meta.json +15 -0
  80. data/lib/multiwoven/integrations/destination/tally/config/spec.json +45 -0
  81. data/lib/multiwoven/integrations/destination/tally/icon.svg +7 -0
  82. data/lib/multiwoven/integrations/protocol/protocol.json +189 -0
  83. data/lib/multiwoven/integrations/protocol/protocol.rb +216 -0
  84. data/lib/multiwoven/integrations/rollout.rb +32 -0
  85. data/lib/multiwoven/integrations/service.rb +79 -0
  86. data/lib/multiwoven/integrations/source/bigquery/client.rb +98 -0
  87. data/lib/multiwoven/integrations/source/bigquery/config/meta.json +15 -0
  88. data/lib/multiwoven/integrations/source/bigquery/config/spec.json +82 -0
  89. data/lib/multiwoven/integrations/source/bigquery/icon.svg +1 -0
  90. data/lib/multiwoven/integrations/source/databricks/client.rb +98 -0
  91. data/lib/multiwoven/integrations/source/databricks/config/meta.json +16 -0
  92. data/lib/multiwoven/integrations/source/databricks/config/spec.json +56 -0
  93. data/lib/multiwoven/integrations/source/databricks/icon.svg +19 -0
  94. data/lib/multiwoven/integrations/source/postgresql/client.rb +109 -0
  95. data/lib/multiwoven/integrations/source/postgresql/config/meta.json +15 -0
  96. data/lib/multiwoven/integrations/source/postgresql/config/spec.json +69 -0
  97. data/lib/multiwoven/integrations/source/postgresql/icon.svg +20 -0
  98. data/lib/multiwoven/integrations/source/redshift/client.rb +109 -0
  99. data/lib/multiwoven/integrations/source/redshift/config/meta.json +15 -0
  100. data/lib/multiwoven/integrations/source/redshift/config/spec.json +71 -0
  101. data/lib/multiwoven/integrations/source/redshift/icon.svg +15 -0
  102. data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/client.rb +123 -0
  103. data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/config/catalog.json +6 -0
  104. data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/config/meta.json +17 -0
  105. data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/config/spec.json +50 -0
  106. data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/icon.svg +16 -0
  107. data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/schema_helper.rb +130 -0
  108. data/lib/multiwoven/integrations/source/snowflake/client.rb +92 -0
  109. data/lib/multiwoven/integrations/source/snowflake/config/meta.json +15 -0
  110. data/lib/multiwoven/integrations/source/snowflake/config/spec.json +82 -0
  111. data/lib/multiwoven/integrations/source/snowflake/icon.svg +10 -0
  112. data/lib/multiwoven/integrations/source/zoho_books/client.rb +155 -0
  113. data/lib/multiwoven/integrations/source/zoho_books/config/meta.json +16 -0
  114. data/lib/multiwoven/integrations/source/zoho_books/config/spec.json +43 -0
  115. data/lib/multiwoven/integrations/source/zoho_books/icon.svg +16 -0
  116. data/lib/multiwoven/integrations.rb +71 -0
  117. data/multiwoven-integrations-0.1.68.gem +0 -0
  118. data/sig/multiwoven/integrations.rbs +6 -0
  119. metadata +515 -0
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Multiwoven
4
+ module Integrations
5
+ VERSION = "0.1.81"
6
+
7
+ ENABLED_SOURCES = %w[
8
+ Snowflake
9
+ Redshift
10
+ Bigquery
11
+ Postgresql
12
+ Databricks
13
+ SalesforceConsumerGoodsCloud
14
+ ZohoBooks
15
+ ].freeze
16
+
17
+ ENABLED_DESTINATIONS = %w[
18
+ Klaviyo
19
+ SalesforceCrm
20
+ FacebookCustomAudience
21
+ Slack
22
+ Hubspot
23
+ GoogleSheets
24
+ Airtable
25
+ Stripe
26
+ SalesforceConsumerGoodsCloud
27
+ Sftp
28
+ Postgresql
29
+ Tally
30
+ ].freeze
31
+ end
32
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Multiwoven
4
+ module Integrations
5
+ class Service
6
+ def initialize
7
+ yield(self.class.config) if block_given?
8
+ end
9
+ class << self
10
+ def connectors
11
+ {
12
+ source: build_connectors(
13
+ ENABLED_SOURCES, "Source"
14
+ ),
15
+ destination: build_connectors(
16
+ ENABLED_DESTINATIONS, "Destination"
17
+ )
18
+ }
19
+ end
20
+
21
+ def connector_class(connector_type, connector_name)
22
+ # *args
23
+ Object.const_get(
24
+ "Multiwoven::Integrations::#{connector_type}::#{connector_name}::Client"
25
+ )
26
+
27
+ # # Retrieve the class dynamically
28
+ # klass = Object.const_get(
29
+ # "Multiwoven::Integrations::#{connector_type}::#{connector_name}::Client"
30
+ # )
31
+
32
+ # # Instantiate the class (assuming it's not a singleton)
33
+ # client_instance = klass.new
34
+
35
+ # # Only apply base_url if connector_name starts with "Zoho" or "zoho"
36
+ # if connector_name.match?(/^Zoho/i)
37
+ # # Check for base_url in the args
38
+ # base_url = args.find { |arg| arg.is_a?(String) && arg.start_with?("http") }
39
+
40
+ # # If base_url is found, call the method to set it
41
+ # if base_url
42
+ # if client_instance.respond_to?(:setting_base_url)
43
+ # client_instance.setting_base_url(base_url)
44
+ # else
45
+ # raise NoMethodError, "Undefined method `setting_base_url` for #{client_instance.class}"
46
+ # end
47
+ # end
48
+ # end
49
+
50
+ # # Return the instance for further use
51
+ # klass
52
+
53
+ end
54
+
55
+ def logger
56
+ config.logger || default_logger
57
+ end
58
+
59
+ def config
60
+ @config ||= Config.new
61
+ end
62
+
63
+ private
64
+
65
+ def build_connectors(enabled_connectors, type)
66
+ enabled_connectors.map do |connector|
67
+ client = connector_class(type, connector).new
68
+ client.meta_data[:data][:connector_spec] = client.connector_spec.to_h
69
+ client.meta_data[:data]
70
+ end
71
+ end
72
+
73
+ def default_logger
74
+ @default_logger ||= Logger.new($stdout)
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "google/cloud/bigquery"
4
+
5
+ module Multiwoven::Integrations::Source
6
+ module Bigquery
7
+ include Multiwoven::Integrations::Core
8
+ class Client < SourceConnector
9
+ def check_connection(connection_config)
10
+ connection_config = connection_config.with_indifferent_access
11
+ bigquery = create_connection(connection_config)
12
+ bigquery.datasets
13
+ ConnectionStatus.new(status: ConnectionStatusType["succeeded"]).to_multiwoven_message
14
+ rescue StandardError => e
15
+ ConnectionStatus.new(status: ConnectionStatusType["failed"], message: e.message).to_multiwoven_message
16
+ end
17
+
18
+ def discover(connection_config)
19
+ connection_config = connection_config.with_indifferent_access
20
+ bigquery = create_connection(connection_config)
21
+ target_dataset_id = connection_config["dataset_id"]
22
+ records = bigquery.datasets.flat_map do |dataset|
23
+ next unless dataset.dataset_id == target_dataset_id
24
+
25
+ dataset.tables.flat_map do |table|
26
+ table.schema.fields.map do |field|
27
+ {
28
+ table_name: table.table_id,
29
+ column_name: field.name,
30
+ data_type: field.type,
31
+ is_nullable: field.mode == "NULLABLE"
32
+ }
33
+ end
34
+ end
35
+ end
36
+ catalog = Catalog.new(streams: create_streams(records))
37
+ catalog.to_multiwoven_message
38
+ rescue StandardError => e
39
+ handle_exception(
40
+ "BIGQUERY:DISCOVER:EXCEPTION",
41
+ "error",
42
+ e
43
+ )
44
+ end
45
+
46
+ def read(sync_config)
47
+ connection_config = sync_config.source.connection_specification
48
+ connection_config = connection_config.with_indifferent_access
49
+ query = sync_config.model.query
50
+
51
+ query = batched_query(query, sync_config.limit, sync_config.offset) unless sync_config.limit.nil? && sync_config.offset.nil?
52
+
53
+ bigquery = create_connection(connection_config)
54
+
55
+ query(bigquery, query)
56
+ rescue StandardError => e
57
+ handle_exception(
58
+ "BIGQUERY:READ:EXCEPTION",
59
+ "error",
60
+ e
61
+ )
62
+ end
63
+
64
+ private
65
+
66
+ def query(connection, query)
67
+ records = []
68
+ results = connection.query(query) || []
69
+ results.each do |row|
70
+ records << RecordMessage.new(data: row, emitted_at: Time.now.to_i).to_multiwoven_message
71
+ end
72
+ records
73
+ end
74
+
75
+ def create_connection(connection_config)
76
+ Google::Cloud::Bigquery.new(
77
+ project: connection_config["project_id"],
78
+ credentials: connection_config["credentials_json"]
79
+ )
80
+ end
81
+
82
+ def create_streams(records)
83
+ group_by_table(records).map do |r|
84
+ Multiwoven::Integrations::Protocol::Stream.new(name: r[:tablename], action: StreamAction["fetch"], json_schema: convert_to_json_schema(r[:columns]))
85
+ end
86
+ end
87
+
88
+ def group_by_table(records)
89
+ records.group_by { |entry| entry[:table_name] }.map do |table_name, columns|
90
+ {
91
+ tablename: table_name,
92
+ columns: columns.map { |column| { column_name: column[:column_name], type: column[:data_type], optional: column[:is_nullable] == "YES" } }
93
+ }
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,15 @@
1
+ {
2
+ "data": {
3
+ "name": "Bigquery",
4
+ "title": "Google BigQuery",
5
+ "connector_type": "source",
6
+ "category": "Data Warehouse",
7
+ "documentation_url": "https://docs.mutliwoven.com",
8
+ "github_issue_label": "source-bigquery",
9
+ "icon": "icon.svg",
10
+ "license": "MIT",
11
+ "release_stage": "alpha",
12
+ "support_level": "community",
13
+ "tags": ["language:ruby", "multiwoven"]
14
+ }
15
+ }
@@ -0,0 +1,82 @@
1
+ {
2
+ "documentation_url": "https://docs.multiwoven.com/integrations/sources/bigquery",
3
+ "stream_type": "dynamic",
4
+ "connector_query_type": "raw_sql",
5
+ "connection_specification": {
6
+ "$schema": "http://json-schema.org/draft-07/schema#",
7
+ "title": "Google BigQuery",
8
+ "type": "object",
9
+ "required": ["project_id", "credentials_json"],
10
+ "properties": {
11
+ "project_id": {
12
+ "type": "string",
13
+ "description": "The project ID, serving as the unique identifier for the Google Cloud project associated with your BigQuery data warehouse, can be located within the Google Cloud web console. This is achieved by either selecting your project name from the top navigation bar or by navigating to the project settings page.",
14
+ "title": "Project ID"
15
+ },
16
+ "dataset_id": {
17
+ "type": "string",
18
+ "description": "The dataset ID to search for tables and views. If you are only loading data from one dataset, setting this option could result in much faster schema discovery. You can get ",
19
+ "title": "Default Dataset ID"
20
+ },
21
+ "credentials_json": {
22
+ "type": "object",
23
+ "description": "You can get the keys from the Google Cloud web console. First, go to the IAM page and select Service Accounts from the left menu. Next, locate your service account in the list, click on its Keys tab, and then click Add Key. Lastly, click Create new key and select JSON.",
24
+ "title": "",
25
+ "properties": {
26
+ "type": {
27
+ "type": "string",
28
+ "enum": ["service_account"]
29
+ },
30
+ "project_id": {
31
+ "type": "string"
32
+ },
33
+ "private_key_id": {
34
+ "type": "string"
35
+ },
36
+ "private_key": {
37
+ "type": "string"
38
+ },
39
+ "client_email": {
40
+ "type": "string",
41
+ "format": "email"
42
+ },
43
+ "client_id": {
44
+ "type": "string"
45
+ },
46
+ "auth_uri": {
47
+ "type": "string",
48
+ "format": "uri"
49
+ },
50
+ "token_uri": {
51
+ "type": "string",
52
+ "format": "uri"
53
+ },
54
+ "auth_provider_x509_cert_url": {
55
+ "type": "string",
56
+ "format": "uri"
57
+ },
58
+ "client_x509_cert_url": {
59
+ "type": "string",
60
+ "format": "uri"
61
+ },
62
+ "universe_domain": {
63
+ "type": "string"
64
+ }
65
+ },
66
+ "required": [
67
+ "type",
68
+ "project_id",
69
+ "private_key_id",
70
+ "private_key",
71
+ "client_email",
72
+ "client_id",
73
+ "auth_uri",
74
+ "token_uri",
75
+ "auth_provider_x509_cert_url",
76
+ "client_x509_cert_url",
77
+ "universe_domain"
78
+ ]
79
+ }
80
+ }
81
+ }
82
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-1.633235433328256 7.0326093303156565 131.26574682416876 114.63439066968435"><linearGradient id="a" gradientUnits="userSpaceOnUse" x1="64" x2="64" y1="7.034" y2="120.789"><stop offset="0" stop-color="#4387fd"/><stop offset="1" stop-color="#4683ea"/></linearGradient><path d="M27.79 115.217L1.54 69.749a11.499 11.499 0 0 1 0-11.499l26.25-45.467a11.5 11.5 0 0 1 9.96-5.75h52.5a11.5 11.5 0 0 1 9.959 5.75l26.25 45.467a11.499 11.499 0 0 1 0 11.5l-26.25 45.467a11.5 11.5 0 0 1-9.959 5.749h-52.5a11.499 11.499 0 0 1-9.96-5.75z" fill="url(#a)"/><path clip-path="url(#b)" d="M119.229 86.48L80.625 47.874 64 43.425l-14.933 5.55L43.3 64l4.637 16.729 40.938 40.938 8.687-.386z" opacity=".07"/><g fill="#fff"><path d="M64 40.804c-12.81 0-23.195 10.385-23.195 23.196 0 12.81 10.385 23.195 23.195 23.195S87.194 76.81 87.194 64c0-12.811-10.385-23.196-23.194-23.196m0 40.795c-9.72 0-17.6-7.88-17.6-17.6S54.28 46.4 64 46.4 81.6 54.28 81.6 64 73.72 81.6 64 81.6"/><path d="M52.99 63.104v7.21a12.794 12.794 0 0 0 4.38 4.475V63.104zM61.675 57.026v19.411c.745.137 1.507.22 2.29.22.714 0 1.41-.075 2.093-.189V57.026zM70.766 66.1v8.562a12.786 12.786 0 0 0 4.382-4.7v-3.861zM80.691 78.287l-2.403 2.405a1.088 1.088 0 0 0 0 1.537l9.115 9.112a1.088 1.088 0 0 0 1.537 0l2.403-2.402a1.092 1.092 0 0 0 0-1.536l-9.116-9.116a1.09 1.09 0 0 0-1.536 0"/></g></svg>
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Multiwoven::Integrations::Source
4
+ module Databricks
5
+ include Multiwoven::Integrations::Core
6
+ class Client < SourceConnector
7
+ def check_connection(connection_config)
8
+ connection_config = connection_config.with_indifferent_access
9
+ create_connection(connection_config)
10
+ ConnectionStatus.new(status: ConnectionStatusType["succeeded"]).to_multiwoven_message
11
+ rescue Sequel::DatabaseConnectionError => e
12
+ ConnectionStatus.new(status: ConnectionStatusType["failed"], message: e.message).to_multiwoven_message
13
+ end
14
+
15
+ def discover(connection_config)
16
+ connection_config = connection_config.with_indifferent_access
17
+ query = "SELECT table_name, column_name, data_type, is_nullable
18
+ FROM system.information_schema.columns
19
+ WHERE table_schema = \'#{connection_config[:schema]}\' AND table_catalog = \'#{connection_config[:catalog]}\'
20
+ ORDER BY table_name, ordinal_position;"
21
+
22
+ db = create_connection(connection_config)
23
+
24
+ records = []
25
+ db.fetch(query.gsub("\n", "")) do |row|
26
+ records << row
27
+ end
28
+ catalog = Catalog.new(streams: create_streams(records))
29
+ catalog.to_multiwoven_message
30
+ rescue StandardError => e
31
+ handle_exception(
32
+ "DATABRICKS:DISCOVER:EXCEPTION",
33
+ "error",
34
+ e
35
+ )
36
+ end
37
+
38
+ def read(sync_config)
39
+ connection_config = sync_config.source.connection_specification
40
+ connection_config = connection_config.with_indifferent_access
41
+ query = sync_config.model.query
42
+ query = batched_query(query, sync_config.limit, sync_config.offset) unless sync_config.limit.nil? && sync_config.offset.nil?
43
+
44
+ db = create_connection(connection_config)
45
+
46
+ query(db, query)
47
+ rescue StandardError => e
48
+ handle_exception(
49
+ "DATABRICKS:READ:EXCEPTION",
50
+ "error",
51
+ e
52
+ )
53
+ end
54
+
55
+ private
56
+
57
+ def query(connection, query)
58
+ records = []
59
+ connection.fetch(query) do |row|
60
+ records << RecordMessage.new(data: row, emitted_at: Time.now.to_i).to_multiwoven_message
61
+ end
62
+ records
63
+ end
64
+
65
+ def create_connection(connection_config)
66
+ Sequel.odbc(drvconnect: generate_drvconnect(connection_config))
67
+ end
68
+
69
+ def generate_drvconnect(connection_config)
70
+ "Driver=#{DATABRICKS_DRIVER_PATH};
71
+ Host=#{connection_config[:host]};
72
+ PORT=#{connection_config[:port]};
73
+ SSL=1;
74
+ HTTPPath=#{connection_config[:http_path]};
75
+ PWD=#{connection_config[:access_token]};
76
+ UID=token;
77
+ ThriftTransport=2;AuthMech=3;
78
+ AllowSelfSignedServerCert=1;
79
+ CAIssuedCertNamesMismatch=1"
80
+ end
81
+
82
+ def create_streams(records)
83
+ group_by_table(records).map do |r|
84
+ Multiwoven::Integrations::Protocol::Stream.new(name: r[:tablename], action: StreamAction["fetch"], json_schema: convert_to_json_schema(r[:columns]))
85
+ end
86
+ end
87
+
88
+ def group_by_table(records)
89
+ records.group_by { |entry| entry[:table_name] }.map do |table_name, columns|
90
+ {
91
+ tablename: table_name,
92
+ columns: columns.map { |column| { column_name: column[:column_name], type: column[:data_type], optional: column[:is_nullable] == "YES" } }
93
+ }
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,16 @@
1
+ {
2
+ "data": {
3
+ "name": "Databricks",
4
+ "title": "Databricks",
5
+ "connector_type": "source",
6
+ "category": "Data Warehouse",
7
+ "documentation_url": "https://docs.mutliwoven.com",
8
+ "github_issue_label": "source-databricks",
9
+ "icon": "icon.svg",
10
+ "license": "MIT",
11
+ "release_stage": "alpha",
12
+ "support_level": "community",
13
+ "tags": ["language:ruby", "multiwoven"]
14
+ }
15
+ }
16
+
@@ -0,0 +1,56 @@
1
+ {
2
+ "documentation_url": "https://docs.multiwoven.com/integrations/sources/databricks",
3
+ "stream_type": "dynamic",
4
+ "connector_query_type": "raw_sql",
5
+ "connection_specification": {
6
+ "$schema": "http://json-schema.org/draft-07/schema#",
7
+ "title": "Databricks",
8
+ "type": "object",
9
+ "required": ["host", "port", "http_path", "catalog", "schema"],
10
+ "properties": {
11
+ "host": {
12
+ "title": "Server Hostname",
13
+ "description": "Server host name for the Databricks Cluster. It is different from the SQL Endpoint Cluster.",
14
+ "type": "string",
15
+ "examples": ["abc-12345678-wxyz.cloud.databricks.com"],
16
+ "order": 0
17
+ },
18
+ "port": {
19
+ "title": "Port",
20
+ "description": "",
21
+ "type": "string",
22
+ "default": "443",
23
+ "order": 1
24
+ },
25
+ "access_token": {
26
+ "title": "Personal Access Token",
27
+ "description": "",
28
+ "type": "string",
29
+ "multiwoven_secret": true,
30
+ "order": 2
31
+ },
32
+ "http_path": {
33
+ "title": "HTTP Path",
34
+ "description": "",
35
+ "examples": ["sql/protocolvx/o/1234567489/0000-1111111-abcd90"],
36
+ "type": "string",
37
+ "order": 3
38
+ },
39
+ "catalog": {
40
+ "description": "The name of the catalog",
41
+ "default": "hive_metastore",
42
+ "type": "string",
43
+ "title": "Databricks catalog",
44
+ "order": 4
45
+ },
46
+ "schema": {
47
+ "description": "The default schema tables are written.",
48
+ "default": "default",
49
+ "type": "string",
50
+ "title": "Database schema",
51
+ "order": 5
52
+ }
53
+ }
54
+ }
55
+ }
56
+
@@ -0,0 +1,19 @@
1
+ <svg version="1.1" id="Layer_1" xmlns:x="ns_extend;" xmlns:i="ns_ai;" xmlns:graph="ns_graphs;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 40.1 42" style="enable-background:new 0 0 40.1 42;" xml:space="preserve">
2
+ <style type="text/css">
3
+ .st0{fill:#FF3621;}
4
+ </style>
5
+ <metadata>
6
+ <sfw xmlns="ns_sfw;">
7
+ <slices>
8
+ </slices>
9
+ <sliceSourceBounds bottomLeftOrigin="true" height="42" width="40.1" x="-69.1" y="-10.5">
10
+ </sliceSourceBounds>
11
+ </sfw>
12
+ </metadata>
13
+ <g>
14
+ <path class="st0" d="M40.1,31.1v-7.4l-0.8-0.5L20.1,33.7l-18.2-10l0-4.3l18.2,9.9l20.1-10.9v-7.3l-0.8-0.5L20.1,21.2L2.6,11.6
15
+ L20.1,2l14.1,7.7l1.1-0.6V8.3L20.1,0L0,10.9V12L20.1,23l18.2-10v4.4l-18.2,10L0.8,16.8L0,17.3v7.4l20.1,10.9l18.2-9.9v4.3l-18.2,10
16
+ L0.8,29.5L0,30v1.1L20.1,42L40.1,31.1z">
17
+ </path>
18
+ </g>
19
+ </svg>
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pg"
4
+
5
+ module Multiwoven::Integrations::Source
6
+ module Postgresql
7
+ include Multiwoven::Integrations::Core
8
+ class Client < SourceConnector
9
+ def check_connection(connection_config)
10
+ connection_config = connection_config.with_indifferent_access
11
+ create_connection(connection_config)
12
+ ConnectionStatus.new(
13
+ status: ConnectionStatusType["succeeded"]
14
+ ).to_multiwoven_message
15
+ rescue PG::Error => e
16
+ ConnectionStatus.new(
17
+ status: ConnectionStatusType["failed"], message: e.message
18
+ ).to_multiwoven_message
19
+ end
20
+
21
+ def discover(connection_config)
22
+ connection_config = connection_config.with_indifferent_access
23
+ query = "SELECT table_name, column_name, data_type, is_nullable
24
+ FROM information_schema.columns
25
+ WHERE table_schema = '#{connection_config[:schema]}' AND table_catalog = '#{connection_config[:database]}'
26
+ ORDER BY table_name, ordinal_position;"
27
+
28
+ db = create_connection(connection_config)
29
+ records = db.exec(query) do |result|
30
+ result.map do |row|
31
+ row
32
+ end
33
+ end
34
+ catalog = Catalog.new(streams: create_streams(records))
35
+ catalog.to_multiwoven_message
36
+ rescue StandardError => e
37
+ handle_exception(
38
+ "POSTGRESQL:DISCOVER:EXCEPTION",
39
+ "error",
40
+ e
41
+ )
42
+ ensure
43
+ db&.close
44
+ end
45
+
46
+ def read(sync_config)
47
+ connection_config = sync_config.source.connection_specification
48
+ connection_config = connection_config.with_indifferent_access
49
+ query = sync_config.model.query
50
+ query = batched_query(query, sync_config.limit, sync_config.offset) unless sync_config.limit.nil? && sync_config.offset.nil?
51
+
52
+ db = create_connection(connection_config)
53
+
54
+ query(db, query)
55
+ rescue StandardError => e
56
+ handle_exception(
57
+ "POSTGRESQL:READ:EXCEPTION",
58
+ "error",
59
+ e
60
+ )
61
+ ensure
62
+ db&.close
63
+ end
64
+
65
+ private
66
+
67
+ def query(connection, query)
68
+ connection.exec(query) do |result|
69
+ result.map do |row|
70
+ RecordMessage.new(data: row, emitted_at: Time.now.to_i).to_multiwoven_message
71
+ end
72
+ end
73
+ end
74
+
75
+ def create_connection(connection_config)
76
+ raise "Unsupported Auth type" unless connection_config[:credentials][:auth_type] == "username/password"
77
+
78
+ PG.connect(
79
+ host: connection_config[:host],
80
+ dbname: connection_config[:database],
81
+ user: connection_config[:credentials][:username],
82
+ password: connection_config[:credentials][:password],
83
+ port: connection_config[:port]
84
+ )
85
+ end
86
+
87
+ def create_streams(records)
88
+ group_by_table(records).map do |r|
89
+ Multiwoven::Integrations::Protocol::Stream.new(name: r[:tablename], action: StreamAction["fetch"], json_schema: convert_to_json_schema(r[:columns]))
90
+ end
91
+ end
92
+
93
+ def group_by_table(records)
94
+ records.group_by { |entry| entry["table_name"] }.map do |table_name, columns|
95
+ {
96
+ tablename: table_name,
97
+ columns: columns.map do |column|
98
+ {
99
+ column_name: column["column_name"],
100
+ type: column["data_type"],
101
+ optional: column["is_nullable"] == "YES"
102
+ }
103
+ end
104
+ }
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,15 @@
1
+ {
2
+ "data": {
3
+ "name": "Postgresql",
4
+ "title": "PostgreSQL",
5
+ "connector_type": "source",
6
+ "category": "Data Warehouse",
7
+ "documentation_url": "https://docs.mutliwoven.com",
8
+ "github_issue_label": "source-postgresql",
9
+ "icon": "icon.svg",
10
+ "license": "MIT",
11
+ "release_stage": "alpha",
12
+ "support_level": "community",
13
+ "tags": ["language:ruby", "multiwoven"]
14
+ }
15
+ }