Skip to content

Commit

Permalink
feat(CE): add oracle db destination connector (#277)
Browse files Browse the repository at this point in the history
  • Loading branch information
TivonB-AI2 authored Aug 2, 2024
1 parent c308382 commit 68dd88d
Show file tree
Hide file tree
Showing 12 changed files with 352 additions and 2 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/integrations-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ jobs:
sudo mv libduckdb/libduckdb.so /usr/local/lib
sudo ldconfig /usr/local/lib
- name: Download and Install Oracle Instant Client
run: |
sudo apt-get install -y libaio1 alien
wget http://yum.oracle.com/repo/OracleLinux/OL7/oracle/instantclient/x86_64/getPackage/oracle-instantclient19.6-basic-19.6.0.0.0-1.x86_64.rpm
wget http://yum.oracle.com/repo/OracleLinux/OL7/oracle/instantclient/x86_64/getPackage/oracle-instantclient19.6-devel-19.6.0.0.0-1.x86_64.rpm
sudo alien -i --scripts oracle-instantclient*.rpm
rm -f oracle-instantclient*.rpm
- name: Install dependencies
run: |
gem install bundler
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/integrations-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@ jobs:
sudo mv libduckdb/libduckdb.so /usr/local/lib
sudo ldconfig /usr/local/lib
- name: Download and Install Oracle Instant Client
run: |
sudo apt-get install -y libaio1 alien
wget http://yum.oracle.com/repo/OracleLinux/OL7/oracle/instantclient/x86_64/getPackage/oracle-instantclient19.6-basic-19.6.0.0.0-1.x86_64.rpm
wget http://yum.oracle.com/repo/OracleLinux/OL7/oracle/instantclient/x86_64/getPackage/oracle-instantclient19.6-devel-19.6.0.0.0-1.x86_64.rpm
sudo alien -i --scripts oracle-instantclient*.rpm
rm -f oracle-instantclient*.rpm
- name: Install dependencies
run: bundle install
working-directory: ./integrations
Expand Down
2 changes: 2 additions & 0 deletions integrations/Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ gem "mysql2"

gem "aws-sdk-sts"

gem "ruby-oci8"

group :development, :test do
gem "simplecov", require: false
gem "simplecov_json_formatter", require: false
Expand Down
6 changes: 5 additions & 1 deletion integrations/Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ GIT
PATH
remote: .
specs:
multiwoven-integrations (0.5.2)
multiwoven-integrations (0.6.0)
activesupport
async-websocket
aws-sdk-athena
Expand All @@ -28,6 +28,7 @@ PATH
rake
restforce
ruby-limiter
ruby-oci8
ruby-odbc
rubyzip
sequel
Expand Down Expand Up @@ -275,6 +276,8 @@ GEM
rubocop-ast (1.31.3)
parser (>= 3.3.1.0)
ruby-limiter (2.3.0)
ruby-oci8 (2.2.12)
ruby-oci8 (2.2.12-x64-mingw-ucrt)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
rubyzip (2.3.2)
Expand Down Expand Up @@ -357,6 +360,7 @@ DEPENDENCIES
rspec (~> 3.0)
rubocop (~> 1.21)
ruby-limiter
ruby-oci8
ruby-odbc!
rubyzip
sequel
Expand Down
2 changes: 2 additions & 0 deletions integrations/lib/multiwoven/integrations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
require "duckdb"
require "iterable-api-client"
require "aws-sdk-sts"
require "ruby-oci8"

# Service
require_relative "integrations/config"
Expand Down Expand Up @@ -78,6 +79,7 @@
require_relative "integrations/destination/iterable/client"
require_relative "integrations/destination/maria_db/client"
require_relative "integrations/destination/databricks_lakehouse/client"
require_relative "integrations/destination/oracle_db/client"

module Multiwoven
module Integrations
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
# frozen_string_literal: true

module Multiwoven::Integrations::Destination
module Oracle
include Multiwoven::Integrations::Core
class Client < DestinationConnector
def check_connection(connection_config)
connection_config = connection_config.with_indifferent_access
create_connection(connection_config)
ConnectionStatus.new(
status: ConnectionStatusType["succeeded"]
).to_multiwoven_message
rescue StandardError => e
ConnectionStatus.new(
status: ConnectionStatusType["failed"], message: e.message
).to_multiwoven_message
end

def discover(connection_config)
records = []
connection_config = connection_config.with_indifferent_access
query = "SELECT table_name, column_name, data_type, nullable
FROM all_tab_columns
WHERE owner = '#{connection_config[:username].upcase}'
ORDER BY table_name, column_id"
conn = create_connection(connection_config)
cursor = conn.exec(query)
while (row = cursor.fetch)
records << row
end
catalog = Catalog.new(streams: create_streams(records))
catalog.to_multiwoven_message
rescue StandardError => e
handle_exception(
"ORACLE:DISCOVER:EXCEPTION",
"error",
e
)
end

def write(sync_config, records, action = "destination_insert")
connection_config = sync_config.destination.connection_specification.with_indifferent_access
table_name = sync_config.stream.name
primary_key = sync_config.model.primary_key
conn = create_connection(connection_config)

write_success = 0
write_failure = 0
log_message_array = []

records.each do |record|
query = Multiwoven::Integrations::Core::QueryBuilder.perform(action, table_name, record, primary_key)
query = query.gsub(";", "")
logger.debug("ORACLE:WRITE:QUERY query = #{query} sync_id = #{sync_config.sync_id} sync_run_id = #{sync_config.sync_run_id}")
begin
response = conn.exec(query)
conn.exec("COMMIT")
write_success += 1
log_message_array << log_request_response("info", query, response)
rescue StandardError => e
handle_exception(e, {
context: "ORACLE:RECORD:WRITE:EXCEPTION",
type: "error",
sync_id: sync_config.sync_id,
sync_run_id: sync_config.sync_run_id
})
write_failure += 1
log_message_array << log_request_response("error", query, e.message)
end
end
tracking_message(write_success, write_failure, log_message_array)
rescue StandardError => e
handle_exception(e, {
context: "ORACLE:RECORD:WRITE:EXCEPTION",
type: "error",
sync_id: sync_config.sync_id,
sync_run_id: sync_config.sync_run_id
})
end

private

def create_connection(connection_config)
OCI8.new(connection_config[:username], connection_config[:password], "#{connection_config[:host]}:#{connection_config[:port]}/#{connection_config[:sid]}")
end

def create_streams(records)
group_by_table(records).map do |_, r|
Multiwoven::Integrations::Protocol::Stream.new(name: r[:tablename], action: StreamAction["fetch"], json_schema: convert_to_json_schema(r[:columns]))
end
end

def group_by_table(records)
result = {}
records.each_with_index do |entry, index|
table_name = entry[0]
column_data = {
column_name: entry[1],
data_type: entry[2],
is_nullable: entry[3] == "Y"
}
result[index] ||= {}
result[index][:tablename] = table_name
result[index][:columns] = [column_data]
end
result.values.group_by { |entry| entry[:tablename] }.transform_values do |entries|
{ tablename: entries.first[:tablename], columns: entries.flat_map { |entry| entry[:columns] } }
end
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"data": {
"name": "Oracle",
"title": "Oracle",
"connector_type": "destination",
"category": "Database",
"documentation_url": "https://docs.squared.ai/guides/data-integration/destination/oracle",
"github_issue_label": "destination-oracle",
"icon": "icon.svg",
"license": "MIT",
"release_stage": "alpha",
"support_level": "community",
"tags": ["language:ruby", "multiwoven"]
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"documentation_url": "https://docs.squared.ai/guides/data-integration/destination/oracle",
"stream_type": "dynamic",
"connector_query_type": "raw_sql",
"connection_specification": {
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Oracle",
"type": "object",
"required": ["host", "port", "sid", "username", "password"],
"properties": {
"host": {
"description": "The Oracle host.",
"examples": ["localhost"],
"type": "string",
"title": "Host",
"order": 0
},
"port": {
"description": "The Oracle port number.",
"examples": ["1521"],
"type": "string",
"title": "Port",
"order": 1
},
"sid": {
"description": "The name of your service in Oracle.",
"examples": ["ORCLPDB1"],
"type": "string",
"title": "SID",
"order": 2
},
"username": {
"description": "The username used to authenticate and connect.",
"type": "string",
"title": "Username",
"order": 3
},
"password": {
"description": "The password corresponding to the username used for authentication.",
"type": "string",
"multiwoven_secret": true,
"title": "Password",
"order": 4
}
}
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion integrations/lib/multiwoven/integrations/rollout.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

module Multiwoven
module Integrations
VERSION = "0.5.2"
VERSION = "0.6.0"

ENABLED_SOURCES = %w[
Snowflake
Expand Down Expand Up @@ -34,6 +34,7 @@ module Integrations
Iterable
MariaDB
DatabricksLakehouse
Oracle
].freeze
end
end
1 change: 1 addition & 0 deletions integrations/multiwoven-integrations.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ Gem::Specification.new do |spec|
spec.add_runtime_dependency "rake"
spec.add_runtime_dependency "restforce"
spec.add_runtime_dependency "ruby-limiter"
spec.add_runtime_dependency "ruby-oci8"
spec.add_runtime_dependency "ruby-odbc"
spec.add_runtime_dependency "rubyzip"
spec.add_runtime_dependency "sequel"
Expand Down
Loading

0 comments on commit 68dd88d

Please sign in to comment.