etl-integrations 0.1.81
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +34 -0
- data/.ruby-version +1 -0
- data/.vscode/settings.json +5 -0
- data/CHANGELOG.md +38 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +105 -0
- data/Rakefile +12 -0
- data/lib/multiwoven/integrations/config.rb +13 -0
- data/lib/multiwoven/integrations/core/base_connector.rb +70 -0
- data/lib/multiwoven/integrations/core/constants.rb +46 -0
- data/lib/multiwoven/integrations/core/destination_connector.rb +14 -0
- data/lib/multiwoven/integrations/core/fullrefresher.rb +19 -0
- data/lib/multiwoven/integrations/core/http_client.rb +34 -0
- data/lib/multiwoven/integrations/core/query_builder.rb +27 -0
- data/lib/multiwoven/integrations/core/rate_limiter.rb +19 -0
- data/lib/multiwoven/integrations/core/source_connector.rb +38 -0
- data/lib/multiwoven/integrations/core/utils.rb +104 -0
- data/lib/multiwoven/integrations/destination/airtable/client.rb +153 -0
- data/lib/multiwoven/integrations/destination/airtable/config/catalog.json +6 -0
- data/lib/multiwoven/integrations/destination/airtable/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/airtable/config/spec.json +22 -0
- data/lib/multiwoven/integrations/destination/airtable/icon.svg +6 -0
- data/lib/multiwoven/integrations/destination/airtable/schema_helper.rb +141 -0
- data/lib/multiwoven/integrations/destination/facebook_custom_audience/client.rb +124 -0
- data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/catalog.json +42 -0
- data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/facebook_custom_audience/config/spec.json +27 -0
- data/lib/multiwoven/integrations/destination/facebook_custom_audience/icon.svg +23 -0
- data/lib/multiwoven/integrations/destination/google_sheets/client.rb +231 -0
- data/lib/multiwoven/integrations/destination/google_sheets/config/catalog.json +6 -0
- data/lib/multiwoven/integrations/destination/google_sheets/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/google_sheets/config/spec.json +74 -0
- data/lib/multiwoven/integrations/destination/google_sheets/icon.svg +1 -0
- data/lib/multiwoven/integrations/destination/hubspot/client.rb +107 -0
- data/lib/multiwoven/integrations/destination/hubspot/config/catalog.json +351 -0
- data/lib/multiwoven/integrations/destination/hubspot/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/hubspot/config/spec.json +17 -0
- data/lib/multiwoven/integrations/destination/hubspot/icon.svg +5 -0
- data/lib/multiwoven/integrations/destination/klaviyo/client.rb +116 -0
- data/lib/multiwoven/integrations/destination/klaviyo/config/catalog.json +103 -0
- data/lib/multiwoven/integrations/destination/klaviyo/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/klaviyo/config/spec.json +22 -0
- data/lib/multiwoven/integrations/destination/klaviyo/icon.svg +6 -0
- data/lib/multiwoven/integrations/destination/postgresql/client.rb +123 -0
- data/lib/multiwoven/integrations/destination/postgresql/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/postgresql/config/spec.json +68 -0
- data/lib/multiwoven/integrations/destination/postgresql/icon.svg +20 -0
- data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/client.rb +114 -0
- data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/config/catalog.json +6 -0
- data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/config/meta.json +16 -0
- data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/config/spec.json +49 -0
- data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/icon.svg +16 -0
- data/lib/multiwoven/integrations/destination/salesforce_consumer_goods_cloud/schema_helper.rb +132 -0
- data/lib/multiwoven/integrations/destination/salesforce_crm/client.rb +117 -0
- data/lib/multiwoven/integrations/destination/salesforce_crm/config/catalog.json +320 -0
- data/lib/multiwoven/integrations/destination/salesforce_crm/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/salesforce_crm/config/spec.json +43 -0
- data/lib/multiwoven/integrations/destination/salesforce_crm/icon.svg +16 -0
- data/lib/multiwoven/integrations/destination/sftp/client.rb +133 -0
- data/lib/multiwoven/integrations/destination/sftp/config/catalog.json +16 -0
- data/lib/multiwoven/integrations/destination/sftp/config/meta.json +16 -0
- data/lib/multiwoven/integrations/destination/sftp/config/spec.json +50 -0
- data/lib/multiwoven/integrations/destination/sftp/icon.svg +1 -0
- data/lib/multiwoven/integrations/destination/slack/client.rb +114 -0
- data/lib/multiwoven/integrations/destination/slack/config/catalog.json +22 -0
- data/lib/multiwoven/integrations/destination/slack/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/slack/config/spec.json +22 -0
- data/lib/multiwoven/integrations/destination/slack/icon.svg +26 -0
- data/lib/multiwoven/integrations/destination/stripe/client.rb +82 -0
- data/lib/multiwoven/integrations/destination/stripe/config/catalog.json +128 -0
- data/lib/multiwoven/integrations/destination/stripe/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/stripe/config/spec.json +17 -0
- data/lib/multiwoven/integrations/destination/stripe/icon.svg +10 -0
- data/lib/multiwoven/integrations/destination/tally/client.rb +151 -0
- data/lib/multiwoven/integrations/destination/tally/config/catalog.json +62 -0
- data/lib/multiwoven/integrations/destination/tally/config/meta.json +15 -0
- data/lib/multiwoven/integrations/destination/tally/config/spec.json +45 -0
- data/lib/multiwoven/integrations/destination/tally/icon.svg +7 -0
- data/lib/multiwoven/integrations/protocol/protocol.json +189 -0
- data/lib/multiwoven/integrations/protocol/protocol.rb +216 -0
- data/lib/multiwoven/integrations/rollout.rb +32 -0
- data/lib/multiwoven/integrations/service.rb +79 -0
- data/lib/multiwoven/integrations/source/bigquery/client.rb +98 -0
- data/lib/multiwoven/integrations/source/bigquery/config/meta.json +15 -0
- data/lib/multiwoven/integrations/source/bigquery/config/spec.json +82 -0
- data/lib/multiwoven/integrations/source/bigquery/icon.svg +1 -0
- data/lib/multiwoven/integrations/source/databricks/client.rb +98 -0
- data/lib/multiwoven/integrations/source/databricks/config/meta.json +16 -0
- data/lib/multiwoven/integrations/source/databricks/config/spec.json +56 -0
- data/lib/multiwoven/integrations/source/databricks/icon.svg +19 -0
- data/lib/multiwoven/integrations/source/postgresql/client.rb +109 -0
- data/lib/multiwoven/integrations/source/postgresql/config/meta.json +15 -0
- data/lib/multiwoven/integrations/source/postgresql/config/spec.json +69 -0
- data/lib/multiwoven/integrations/source/postgresql/icon.svg +20 -0
- data/lib/multiwoven/integrations/source/redshift/client.rb +109 -0
- data/lib/multiwoven/integrations/source/redshift/config/meta.json +15 -0
- data/lib/multiwoven/integrations/source/redshift/config/spec.json +71 -0
- data/lib/multiwoven/integrations/source/redshift/icon.svg +15 -0
- data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/client.rb +123 -0
- data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/config/catalog.json +6 -0
- data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/config/meta.json +17 -0
- data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/config/spec.json +50 -0
- data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/icon.svg +16 -0
- data/lib/multiwoven/integrations/source/salesforce_consumer_goods_cloud/schema_helper.rb +130 -0
- data/lib/multiwoven/integrations/source/snowflake/client.rb +92 -0
- data/lib/multiwoven/integrations/source/snowflake/config/meta.json +15 -0
- data/lib/multiwoven/integrations/source/snowflake/config/spec.json +82 -0
- data/lib/multiwoven/integrations/source/snowflake/icon.svg +10 -0
- data/lib/multiwoven/integrations/source/zoho_books/client.rb +155 -0
- data/lib/multiwoven/integrations/source/zoho_books/config/meta.json +16 -0
- data/lib/multiwoven/integrations/source/zoho_books/config/spec.json +43 -0
- data/lib/multiwoven/integrations/source/zoho_books/icon.svg +16 -0
- data/lib/multiwoven/integrations.rb +71 -0
- data/multiwoven-integrations-0.1.68.gem +0 -0
- data/sig/multiwoven/integrations.rbs +6 -0
- 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
|
+
}
|