etl-integrations 0.1.81
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
}
|