multiwoven-integrations 0.1.0
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/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/LICENSE.txt +21 -0
- data/README.md +43 -0
- data/Rakefile +12 -0
- data/lib/multiwoven/integrations/config.rb +13 -0
- data/lib/multiwoven/integrations/core/base_connector.rb +44 -0
- data/lib/multiwoven/integrations/core/constants.rb +34 -0
- data/lib/multiwoven/integrations/core/destination_connector.rb +12 -0
- data/lib/multiwoven/integrations/core/http_client.rb +34 -0
- data/lib/multiwoven/integrations/core/source_connector.rb +12 -0
- data/lib/multiwoven/integrations/core/utils.rb +68 -0
- data/lib/multiwoven/integrations/destination/klaviyo/client.rb +119 -0
- data/lib/multiwoven/integrations/destination/klaviyo/config/catalog.json +124 -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/protocol/protocol.json +189 -0
- data/lib/multiwoven/integrations/protocol/protocol.rb +179 -0
- data/lib/multiwoven/integrations/rollout.rb +17 -0
- data/lib/multiwoven/integrations/service.rb +57 -0
- data/lib/multiwoven/integrations/source/bigquery/client.rb +86 -0
- data/lib/multiwoven/integrations/source/bigquery/config/meta.json +15 -0
- data/lib/multiwoven/integrations/source/bigquery/config/spec.json +28 -0
- data/lib/multiwoven/integrations/source/redshift/client.rb +100 -0
- data/lib/multiwoven/integrations/source/redshift/config/meta.json +15 -0
- data/lib/multiwoven/integrations/source/redshift/config/spec.json +74 -0
- data/lib/multiwoven/integrations/source/snowflake/client.rb +84 -0
- data/lib/multiwoven/integrations/source/snowflake/config/meta.json +15 -0
- data/lib/multiwoven/integrations/source/snowflake/config/spec.json +87 -0
- data/lib/multiwoven/integrations.rb +41 -0
- data/multiwoven-integrations.gemspec +54 -0
- data/sig/multiwoven/integrations.rbs +6 -0
- metadata +291 -0
@@ -0,0 +1,124 @@
|
|
1
|
+
{
|
2
|
+
"streams": [
|
3
|
+
{
|
4
|
+
"name": "profile",
|
5
|
+
"action": "create",
|
6
|
+
"url": "https://a.klaviyo.com/api/profiles",
|
7
|
+
"method": "POST",
|
8
|
+
"json_schema": {
|
9
|
+
"type": "object",
|
10
|
+
"additionalProperties": true,
|
11
|
+
"properties": {
|
12
|
+
"type": {
|
13
|
+
"type": [
|
14
|
+
"null",
|
15
|
+
"string"
|
16
|
+
]
|
17
|
+
},
|
18
|
+
"id": {
|
19
|
+
"type": "string"
|
20
|
+
},
|
21
|
+
"updated": {
|
22
|
+
"type": [
|
23
|
+
"null",
|
24
|
+
"string"
|
25
|
+
],
|
26
|
+
"format": "date-time"
|
27
|
+
},
|
28
|
+
"attributes": {
|
29
|
+
"type": [
|
30
|
+
"null",
|
31
|
+
"object"
|
32
|
+
],
|
33
|
+
"additionalProperties": true,
|
34
|
+
"properties": {
|
35
|
+
"email": {
|
36
|
+
"type": [
|
37
|
+
"null",
|
38
|
+
"string"
|
39
|
+
]
|
40
|
+
},
|
41
|
+
"phone_number": {
|
42
|
+
"type": [
|
43
|
+
"null",
|
44
|
+
"string"
|
45
|
+
]
|
46
|
+
},
|
47
|
+
"first_name": {
|
48
|
+
"type": [
|
49
|
+
"null",
|
50
|
+
"string"
|
51
|
+
]
|
52
|
+
},
|
53
|
+
"last_name": {
|
54
|
+
"type": [
|
55
|
+
"null",
|
56
|
+
"string"
|
57
|
+
]
|
58
|
+
},
|
59
|
+
"properties": {
|
60
|
+
"type": [
|
61
|
+
"null",
|
62
|
+
"object"
|
63
|
+
],
|
64
|
+
"additionalProperties": true
|
65
|
+
},
|
66
|
+
"organization": {
|
67
|
+
"type": [
|
68
|
+
"null",
|
69
|
+
"string"
|
70
|
+
]
|
71
|
+
},
|
72
|
+
"title": {
|
73
|
+
"type": [
|
74
|
+
"null",
|
75
|
+
"string"
|
76
|
+
]
|
77
|
+
},
|
78
|
+
"last_event_date": {
|
79
|
+
"type": [
|
80
|
+
"null",
|
81
|
+
"string"
|
82
|
+
],
|
83
|
+
"format": "date-time"
|
84
|
+
}
|
85
|
+
}
|
86
|
+
},
|
87
|
+
"links": {
|
88
|
+
"type": [
|
89
|
+
"null",
|
90
|
+
"object"
|
91
|
+
]
|
92
|
+
},
|
93
|
+
"relationships": {
|
94
|
+
"type": [
|
95
|
+
"null",
|
96
|
+
"object"
|
97
|
+
]
|
98
|
+
},
|
99
|
+
"segments": {
|
100
|
+
"type": [
|
101
|
+
"null",
|
102
|
+
"object"
|
103
|
+
]
|
104
|
+
}
|
105
|
+
}
|
106
|
+
},
|
107
|
+
"supported_sync_modes": [
|
108
|
+
"full_refresh",
|
109
|
+
"incremental"
|
110
|
+
],
|
111
|
+
"source_defined_cursor": true,
|
112
|
+
"default_cursor_field": [
|
113
|
+
"updated"
|
114
|
+
],
|
115
|
+
"source_defined_primary_key": [
|
116
|
+
[
|
117
|
+
"id",
|
118
|
+
"email"
|
119
|
+
]
|
120
|
+
]
|
121
|
+
|
122
|
+
}
|
123
|
+
]
|
124
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
{
|
2
|
+
"data":
|
3
|
+
{
|
4
|
+
"name": "Klaviyo",
|
5
|
+
"connector_type": "destination",
|
6
|
+
"connector_subtype": "API",
|
7
|
+
"documentation_url": "https://docs.mutliwoven.com",
|
8
|
+
"github_issue_label": "destination-klaviyo",
|
9
|
+
"icon": "klaviyo.svg",
|
10
|
+
"license": "MIT",
|
11
|
+
"release_stage": "alpha",
|
12
|
+
"support_level": "community",
|
13
|
+
"tags": ["language:ruby", "multiwoven"]
|
14
|
+
}
|
15
|
+
}
|
@@ -0,0 +1,22 @@
|
|
1
|
+
{
|
2
|
+
"documentation_url": "https://docs.multiwoven.com/integrations/destination/klaviyo",
|
3
|
+
"stream_type": "static",
|
4
|
+
"connection_specification": {
|
5
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
6
|
+
"title": "Klaviyo Destination Spec",
|
7
|
+
"type": "object",
|
8
|
+
"required": ["public_api_key", "private_api_key"],
|
9
|
+
"properties": {
|
10
|
+
"public_api_keyhost": {
|
11
|
+
"type": "string",
|
12
|
+
"title": "Public API Key",
|
13
|
+
"order": 0
|
14
|
+
},
|
15
|
+
"private_api_key": {
|
16
|
+
"type": "string",
|
17
|
+
"title": "Private API Key",
|
18
|
+
"order": 1
|
19
|
+
}
|
20
|
+
}
|
21
|
+
}
|
22
|
+
}
|
@@ -0,0 +1,189 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
3
|
+
"title": "Mutliwoven Protocol",
|
4
|
+
"type": "object",
|
5
|
+
"description": "Mutliwoven protocol schema",
|
6
|
+
"version": "1.0.0",
|
7
|
+
"definitions": {
|
8
|
+
"SyncMode": {
|
9
|
+
"type": "string",
|
10
|
+
"enum": [
|
11
|
+
"full_refresh",
|
12
|
+
"incremental"
|
13
|
+
]
|
14
|
+
},
|
15
|
+
"SyncStatus": {
|
16
|
+
"type": "string",
|
17
|
+
"enum": [
|
18
|
+
"STARTED",
|
19
|
+
"RUNNING",
|
20
|
+
"COMPLETE",
|
21
|
+
"INCOMPLETE"
|
22
|
+
]
|
23
|
+
},
|
24
|
+
"DestinationSyncMode": {
|
25
|
+
"type": "string",
|
26
|
+
"enum": [
|
27
|
+
"append",
|
28
|
+
"overwrite",
|
29
|
+
"append_dedup"
|
30
|
+
]
|
31
|
+
},
|
32
|
+
"ProtocolModel": {
|
33
|
+
"type": "object",
|
34
|
+
"additionalProperties": true
|
35
|
+
},
|
36
|
+
"ConnectionStatus": {
|
37
|
+
"type": "object",
|
38
|
+
"properties": {
|
39
|
+
"status": {
|
40
|
+
"$ref": "#/definitions/Types/String.enum(SUCCEEDED,FAILED)"
|
41
|
+
},
|
42
|
+
"message": {
|
43
|
+
"type": "string"
|
44
|
+
}
|
45
|
+
},
|
46
|
+
"additionalProperties": true
|
47
|
+
},
|
48
|
+
"ConnectorSpecification": {
|
49
|
+
"type": "object",
|
50
|
+
"properties": {
|
51
|
+
"documentation_url": {
|
52
|
+
"type": "string"
|
53
|
+
},
|
54
|
+
"changelog_url": {
|
55
|
+
"type": "string"
|
56
|
+
},
|
57
|
+
"connection_specification": {
|
58
|
+
"type": "object"
|
59
|
+
},
|
60
|
+
"supports_normalization": {
|
61
|
+
"type": "boolean",
|
62
|
+
"default": false
|
63
|
+
},
|
64
|
+
"supports_dbt": {
|
65
|
+
"type": "boolean",
|
66
|
+
"default": false
|
67
|
+
},
|
68
|
+
"supported_destination_sync_modes": {
|
69
|
+
"type": "array",
|
70
|
+
"items": {
|
71
|
+
"$ref": "#/definitions/DestinationSyncMode"
|
72
|
+
}
|
73
|
+
}
|
74
|
+
},
|
75
|
+
"additionalProperties": true
|
76
|
+
},
|
77
|
+
"LogMessage": {
|
78
|
+
"type": "object",
|
79
|
+
"properties": {
|
80
|
+
"level": {
|
81
|
+
"$ref": "#/definitions/Types/String.enum(FATAL,ERROR,WARN,INFO,DEBUG,TRACE)"
|
82
|
+
},
|
83
|
+
"message": {
|
84
|
+
"type": "string"
|
85
|
+
},
|
86
|
+
"stack_trace": {
|
87
|
+
"type": "string"
|
88
|
+
}
|
89
|
+
},
|
90
|
+
"additionalProperties": true
|
91
|
+
},
|
92
|
+
"RecordMessage": {
|
93
|
+
"type": "object",
|
94
|
+
"properties": {
|
95
|
+
"stream": {
|
96
|
+
"type": "string"
|
97
|
+
},
|
98
|
+
"data": {
|
99
|
+
"type": "object"
|
100
|
+
},
|
101
|
+
"emitted_at": {
|
102
|
+
"type": "integer"
|
103
|
+
}
|
104
|
+
},
|
105
|
+
"additionalProperties": true
|
106
|
+
},
|
107
|
+
"Stream": {
|
108
|
+
"type": "object",
|
109
|
+
"properties": {
|
110
|
+
"name": {
|
111
|
+
"type": "string"
|
112
|
+
},
|
113
|
+
"json_schema": {
|
114
|
+
"type": "object"
|
115
|
+
},
|
116
|
+
"supported_sync_modes": {
|
117
|
+
"type": "array",
|
118
|
+
"items": {
|
119
|
+
"$ref": "#/definitions/SyncMode"
|
120
|
+
}
|
121
|
+
},
|
122
|
+
"source_defined_cursor": {
|
123
|
+
"type": "boolean"
|
124
|
+
},
|
125
|
+
"default_cursor_field": {
|
126
|
+
"type": "array",
|
127
|
+
"items": {
|
128
|
+
"type": "string"
|
129
|
+
}
|
130
|
+
},
|
131
|
+
"source_defined_primary_key": {
|
132
|
+
"type": "array",
|
133
|
+
"items": {
|
134
|
+
"type": "array",
|
135
|
+
"items": {
|
136
|
+
"type": "string"
|
137
|
+
}
|
138
|
+
}
|
139
|
+
},
|
140
|
+
"namespace": {
|
141
|
+
"type": "string"
|
142
|
+
}
|
143
|
+
},
|
144
|
+
"additionalProperties": true
|
145
|
+
},
|
146
|
+
"Catalog": {
|
147
|
+
"type": "object",
|
148
|
+
"properties": {
|
149
|
+
"streams": {
|
150
|
+
"type": "array",
|
151
|
+
"items": {
|
152
|
+
"$ref": "#/definitions/Stream"
|
153
|
+
}
|
154
|
+
}
|
155
|
+
},
|
156
|
+
"additionalProperties": true
|
157
|
+
},
|
158
|
+
"SyncConfig": {
|
159
|
+
"type": "object",
|
160
|
+
"properties": {
|
161
|
+
"stream": {
|
162
|
+
"$ref": "#/definitions/Stream"
|
163
|
+
},
|
164
|
+
"sync_mode": {
|
165
|
+
"$ref": "#/definitions/SyncMode"
|
166
|
+
},
|
167
|
+
"cursor_field": {
|
168
|
+
"type": "array",
|
169
|
+
"items": {
|
170
|
+
"type": "string"
|
171
|
+
}
|
172
|
+
},
|
173
|
+
"destination_sync_mode": {
|
174
|
+
"$ref": "#/definitions/DestinationSyncMode"
|
175
|
+
},
|
176
|
+
"primary_key": {
|
177
|
+
"type": "array",
|
178
|
+
"items": {
|
179
|
+
"type": "array",
|
180
|
+
"items": {
|
181
|
+
"type": "string"
|
182
|
+
}
|
183
|
+
}
|
184
|
+
}
|
185
|
+
},
|
186
|
+
"additionalProperties": true
|
187
|
+
}
|
188
|
+
}
|
189
|
+
}
|
@@ -0,0 +1,179 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Multiwoven
|
4
|
+
module Integrations::Protocol
|
5
|
+
module Types
|
6
|
+
include Dry.Types()
|
7
|
+
end
|
8
|
+
|
9
|
+
SyncMode = Types::String.enum("full_refresh", "incremental")
|
10
|
+
SyncStatus = Types::String.enum("started", "running", "complete", "incomplete")
|
11
|
+
DestinationSyncMode = Types::String.enum("insert", "upsert")
|
12
|
+
ConnectorType = Types::String.enum("source", "destination")
|
13
|
+
ModelQueryType = Types::String.enum("raw_sql", "dbt")
|
14
|
+
ConnectionStatusType = Types::String.enum("succeeded", "failed")
|
15
|
+
StreamType = Types::String.enum("static", "dynamic")
|
16
|
+
StreamAction = Types::String.enum("fetch", "create", "update", "delete")
|
17
|
+
MultiwovenMessageType = Types::String.enum(
|
18
|
+
"record", "log", "connector_spec",
|
19
|
+
"connection_status", "catalog", "control",
|
20
|
+
"tracking"
|
21
|
+
)
|
22
|
+
ControlMessageType = Types::String.enum(
|
23
|
+
"rate_limit", "connection_config"
|
24
|
+
)
|
25
|
+
LogLevel = Types::String.enum("fatal", "error", "warn", "info", "debug", "trace")
|
26
|
+
|
27
|
+
class ProtocolModel < Dry::Struct
|
28
|
+
extend Multiwoven::Integrations::Core::Utils
|
29
|
+
class << self
|
30
|
+
def from_json(json_string)
|
31
|
+
data = JSON.parse(json_string)
|
32
|
+
new(keys_to_symbols(data))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class ConnectionStatus < ProtocolModel
|
38
|
+
attribute :status, ConnectionStatusType
|
39
|
+
attribute? :message, Types::String.optional
|
40
|
+
|
41
|
+
def to_multiwoven_message
|
42
|
+
MultiwovenMessage.new(
|
43
|
+
type: MultiwovenMessageType["connection_status"],
|
44
|
+
connection_status: self
|
45
|
+
)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
class ConnectorSpecification < ProtocolModel
|
50
|
+
attribute? :documentation_url, Types::String.optional
|
51
|
+
attribute? :changelog_url, Types::String.optional
|
52
|
+
attribute :connection_specification, Types::Hash
|
53
|
+
attribute :supports_normalization, Types::Bool.default(false)
|
54
|
+
attribute :supports_dbt, Types::Bool.default(false)
|
55
|
+
attribute :stream_type, StreamType
|
56
|
+
attribute? :supported_destination_sync_modes, Types::Array.of(DestinationSyncMode).optional
|
57
|
+
|
58
|
+
def to_multiwoven_message
|
59
|
+
MultiwovenMessage.new(
|
60
|
+
type: MultiwovenMessageType["connector_spec"],
|
61
|
+
connector_spec: self
|
62
|
+
)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class Connector < ProtocolModel
|
67
|
+
attribute :name, Types::String
|
68
|
+
attribute :type, ConnectorType
|
69
|
+
attribute :connection_specification, Types::Hash
|
70
|
+
end
|
71
|
+
|
72
|
+
class LogMessage < ProtocolModel
|
73
|
+
attribute :level, LogLevel
|
74
|
+
attribute :message, Types::String
|
75
|
+
attribute? :name, Types::String.optional
|
76
|
+
attribute? :stack_trace, Types::String.optional
|
77
|
+
|
78
|
+
def to_multiwoven_message
|
79
|
+
MultiwovenMessage.new(
|
80
|
+
type: MultiwovenMessageType["log"],
|
81
|
+
log: self
|
82
|
+
)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class Model < ProtocolModel
|
87
|
+
attribute? :name, Types::String.optional
|
88
|
+
attribute :query, Types::String
|
89
|
+
attribute :query_type, ModelQueryType
|
90
|
+
attribute :primary_key, Types::String
|
91
|
+
end
|
92
|
+
|
93
|
+
class RecordMessage < ProtocolModel
|
94
|
+
attribute :data, Types::Hash
|
95
|
+
attribute :emitted_at, Types::Integer
|
96
|
+
|
97
|
+
def to_multiwoven_message
|
98
|
+
MultiwovenMessage.new(
|
99
|
+
type: MultiwovenMessageType["record"],
|
100
|
+
record: self
|
101
|
+
)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
class Stream < ProtocolModel
|
106
|
+
# Common
|
107
|
+
attribute :name, Types::String
|
108
|
+
attribute? :action, StreamAction
|
109
|
+
attribute :json_schema, Types::Hash
|
110
|
+
attribute? :supported_sync_modes, Types::Array.of(SyncMode).optional
|
111
|
+
|
112
|
+
attribute? :source_defined_cursor, Types::Bool.optional
|
113
|
+
attribute? :default_cursor_field, Types::Array.of(Types::String).optional
|
114
|
+
attribute? :source_defined_primary_key, Types::Array.of(Types::Array.of(Types::String)).optional
|
115
|
+
attribute? :namespace, Types::String.optional
|
116
|
+
# Applicable for API streams
|
117
|
+
attribute? :url, Types::String.optional
|
118
|
+
attribute? :request_method, Types::String.optional
|
119
|
+
end
|
120
|
+
|
121
|
+
class Catalog < ProtocolModel
|
122
|
+
attribute :streams, Types::Array.of(Stream)
|
123
|
+
|
124
|
+
def to_multiwoven_message
|
125
|
+
MultiwovenMessage.new(
|
126
|
+
type: MultiwovenMessageType["catalog"],
|
127
|
+
catalog: self
|
128
|
+
)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
class SyncConfig < ProtocolModel
|
133
|
+
attribute :source, Connector
|
134
|
+
attribute :destination, Connector
|
135
|
+
attribute :model, Model
|
136
|
+
attribute :stream, Stream
|
137
|
+
attribute :sync_mode, SyncMode
|
138
|
+
attribute? :cursor_field, Types::String.optional
|
139
|
+
attribute :destination_sync_mode, DestinationSyncMode
|
140
|
+
end
|
141
|
+
|
142
|
+
class ControlMessage < ProtocolModel
|
143
|
+
attribute :type, ControlMessageType
|
144
|
+
attribute :emitted_at, Types::Integer
|
145
|
+
attribute? :meta, Types::Hash
|
146
|
+
|
147
|
+
def to_multiwoven_message
|
148
|
+
MultiwovenMessage.new(
|
149
|
+
type: MultiwovenMessageType["control"],
|
150
|
+
control: self
|
151
|
+
)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class TrackingMessage < ProtocolModel
|
156
|
+
attribute :success, Types::Integer.default(0)
|
157
|
+
attribute :failed, Types::Integer.default(0)
|
158
|
+
attribute? :meta, Types::Hash
|
159
|
+
|
160
|
+
def to_multiwoven_message
|
161
|
+
MultiwovenMessage.new(
|
162
|
+
type: MultiwovenMessageType["tracking"],
|
163
|
+
tracking: self
|
164
|
+
)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
class MultiwovenMessage < ProtocolModel
|
169
|
+
attribute :type, MultiwovenMessageType
|
170
|
+
attribute? :log, LogMessage.optional
|
171
|
+
attribute? :connection_status, ConnectionStatus.optional
|
172
|
+
attribute? :connector_spec, ConnectorSpecification.optional
|
173
|
+
attribute? :catalog, Catalog.optional
|
174
|
+
attribute? :record, RecordMessage.optional
|
175
|
+
attribute? :control, ControlMessage.optional
|
176
|
+
attribute? :tracking, TrackingMessage.optional
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Multiwoven
|
4
|
+
module Integrations
|
5
|
+
class Service
|
6
|
+
class << self
|
7
|
+
def initialize
|
8
|
+
yield(config) if block_given?
|
9
|
+
end
|
10
|
+
|
11
|
+
def connectors
|
12
|
+
{
|
13
|
+
source: build_connectors(
|
14
|
+
ENABLED_SOURCES, "Source"
|
15
|
+
),
|
16
|
+
destination: build_connectors(
|
17
|
+
ENABLED_DESTINATIONS, "Destination"
|
18
|
+
)
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def connector_class(connector_type, connector_name)
|
23
|
+
Object.const_get(
|
24
|
+
"Multiwoven::Integrations::#{connector_type}::#{connector_name}::Client"
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
def logger
|
29
|
+
config.logger || default_logger
|
30
|
+
end
|
31
|
+
|
32
|
+
def config
|
33
|
+
@config ||= Config.new
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def build_connectors(enabled_connectors, type)
|
39
|
+
enabled_connectors.map do |connector|
|
40
|
+
client = connector_class(
|
41
|
+
type, connector
|
42
|
+
).new
|
43
|
+
client.meta_data["data"].to_h.merge(
|
44
|
+
{
|
45
|
+
connector_spec: client.connector_spec.to_h
|
46
|
+
}
|
47
|
+
)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def default_logger
|
52
|
+
@default_logger ||= Logger.new($stdout)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,86 @@
|
|
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
|
+
bigquery = create_connection(connection_config)
|
11
|
+
bigquery.datasets
|
12
|
+
ConnectionStatus.new(status: ConnectionStatusType["succeeded"]).to_multiwoven_message
|
13
|
+
rescue StandardError => e
|
14
|
+
ConnectionStatus.new(status: ConnectionStatusType["failed"], message: e.message).to_multiwoven_message
|
15
|
+
end
|
16
|
+
|
17
|
+
def discover(connection_config)
|
18
|
+
bigquery = create_connection(connection_config)
|
19
|
+
target_dataset_id = connection_config["dataset_id"]
|
20
|
+
records = bigquery.datasets.flat_map do |dataset|
|
21
|
+
next unless dataset.dataset_id == target_dataset_id
|
22
|
+
|
23
|
+
dataset.tables.flat_map do |table|
|
24
|
+
table.schema.fields.map do |field|
|
25
|
+
{
|
26
|
+
table_name: table.table_id,
|
27
|
+
column_name: field.name,
|
28
|
+
data_type: field.type,
|
29
|
+
is_nullable: field.mode == "NULLABLE"
|
30
|
+
}
|
31
|
+
end
|
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
|
+
"BIGQUERY:DISCOVER:EXCEPTION",
|
39
|
+
"error",
|
40
|
+
e
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
def read(sync_config)
|
45
|
+
connection_config = sync_config.source.connection_specification
|
46
|
+
query = sync_config.model.query
|
47
|
+
bigquery = create_connection(connection_config)
|
48
|
+
records = []
|
49
|
+
results = bigquery.query query
|
50
|
+
results.each do |row|
|
51
|
+
records << RecordMessage.new(data: row, emitted_at: Time.now.to_i)
|
52
|
+
end
|
53
|
+
|
54
|
+
records
|
55
|
+
rescue StandardError => e
|
56
|
+
handle_exception(
|
57
|
+
"BIGQUERY:READ:EXCEPTION",
|
58
|
+
"error",
|
59
|
+
e
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def create_connection(connection_config)
|
64
|
+
Google::Cloud::Bigquery.new(
|
65
|
+
project: connection_config["project_id"],
|
66
|
+
credentials: connection_config["credentials_json"]
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_streams(records)
|
71
|
+
group_by_table(records).map do |r|
|
72
|
+
Multiwoven::Integrations::Protocol::Stream.new(name: r[:tablename], action: StreamAction["fetch"], json_schema: convert_to_json_schema(r[:columns]))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def group_by_table(records)
|
77
|
+
records.group_by { |entry| entry[:table_name] }.map do |table_name, columns|
|
78
|
+
{
|
79
|
+
tablename: table_name,
|
80
|
+
columns: columns.map { |column| { column_name: column[:column_name], type: column[:data_type], optional: column[:is_nullable] == "YES" } }
|
81
|
+
}
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|