Skip to content

Commit 6c89645

Browse files
author
gdgate
authored
Merge pull request #1743 from tqtu/MSF-20118-3
FEATURE: MSF-20118 Support PostgreSQL as input for LCM provisioning bricks Reviewed-by: https://github.com/danh-ung
2 parents 2783f1b + 3b9532a commit 6c89645

7 files changed

Lines changed: 178 additions & 3 deletions

File tree

Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ RUN cp -rf ci/snowflake/target/*.jar ./lib/gooddata/cloud_resources/snowflake/dr
6464
RUN mvn -f ci/bigquery/pom.xml clean install -P binary-packaging
6565
RUN cp -rf ci/bigquery/target/*.jar ./lib/gooddata/cloud_resources/bigquery/drivers/
6666

67+
#build postgresql dependencies
68+
RUN mvn -f ci/postgresql/pom.xml clean install -P binary-packaging
69+
RUN cp -rf ci/postgresql/target/*.jar ./lib/gooddata/cloud_resources/postgresql/drivers/
70+
6771
RUN bundle install
6872

6973
ARG GIT_COMMIT=unspecified

ci/postgresql/pom.xml

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<groupId>com.gooddata.lcm</groupId>
8+
<artifactId>lcm-postgresql-driver</artifactId>
9+
<version>1.0-SNAPSHOT</version>
10+
11+
<dependencies>
12+
<dependency>
13+
<groupId>org.postgresql</groupId>
14+
<artifactId>postgresql</artifactId>
15+
<version>42.2.19</version>
16+
</dependency>
17+
<dependency>
18+
<groupId>org.slf4j</groupId>
19+
<artifactId>slf4j-api</artifactId>
20+
<version>1.7.2</version>
21+
</dependency>
22+
</dependencies>
23+
24+
<profiles>
25+
<profile>
26+
<id>binary-packaging</id>
27+
<build>
28+
<plugins>
29+
<plugin>
30+
<artifactId>maven-dependency-plugin</artifactId>
31+
<executions>
32+
<execution>
33+
<phase>package</phase>
34+
<goals>
35+
<goal>copy-dependencies</goal>
36+
</goals>
37+
<configuration>
38+
<outputDirectory>${project.build.directory}</outputDirectory>
39+
<!-- compile scope gives runtime and compile dependencies (skips test deps) -->
40+
<includeScope>runtime</includeScope>
41+
</configuration>
42+
</execution>
43+
</executions>
44+
</plugin>
45+
</plugins>
46+
</build>
47+
</profile>
48+
</profiles>
49+
50+
<repositories>
51+
<repository>
52+
<id>my-repo1</id>
53+
<name>my custom repo</name>
54+
<url>https://repository.mulesoft.org/nexus/content/repositories/public/</url>
55+
</repository>
56+
</repositories>
57+
</project>

lib/gooddata/cloud_resources/cloud_resouce_factory.rb renamed to lib/gooddata/cloud_resources/cloud_resource_factory.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,15 @@ module GoodData
1111
module CloudResources
1212
class CloudResourceFactory
1313
class << self
14+
def load_cloud_resource(type)
15+
base = "#{Pathname(__FILE__).dirname.expand_path}#{File::SEPARATOR}#{type}#{File::SEPARATOR}"
16+
Dir.glob(base + '**/*.rb').each do |file|
17+
require file
18+
end
19+
end
20+
1421
def create(type, data = {}, opts = {})
22+
load_cloud_resource(type)
1523
clients = CloudResourceClient.descendants.select { |c| c.respond_to?("accept?") && c.send("accept?", type) }
1624
raise "DataSource does not support type \"#{type}\"" if clients.empty?
1725

lib/gooddata/cloud_resources/cloud_resources.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@
77
require 'pathname'
88

99
base = Pathname(__FILE__).dirname.expand_path
10-
Dir.glob(base + '**/*.rb').each do |file|
10+
Dir.glob(base + '*.rb').each do |file|
1111
require file
1212
end

lib/gooddata/cloud_resources/postgresql/drivers/.gitkeepme

Whitespace-only changes.
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# encoding: UTF-8
2+
# frozen_string_literal: true
3+
#
4+
# Copyright (c) 2021 GoodData Corporation. All rights reserved.
5+
# This source code is licensed under the BSD-style license found in the
6+
# LICENSE file in the root directory of this source tree.
7+
8+
require 'securerandom'
9+
require 'java'
10+
require 'pathname'
11+
require_relative '../cloud_resource_client'
12+
13+
base = Pathname(__FILE__).dirname.expand_path
14+
Dir.glob(base + 'drivers/*.jar').each do |file|
15+
require file unless file.start_with?('lcm-postgresql-driver')
16+
end
17+
18+
module GoodData
19+
module CloudResources
20+
class PostgresClient < CloudResourceClient
21+
JDBC_POSTGRES_PATTERN = %r{jdbc:postgresql:\/\/([^:^\/]+)(:([0-9]+))?(\/)?}
22+
POSTGRES_DEFAULT_PORT = 5432
23+
JDBC_POSTGRES_PROTOCOL = 'jdbc:postgresql://'
24+
SSL_JAVA_FACTORY = '&sslfactory=org.postgresql.ssl.DefaultJavaSSLFactory'
25+
VERIFY_FULL = 'verify-full'
26+
PREFER = 'prefer'
27+
REQUIRE = 'require'
28+
POSTGRES_SET_SCHEMA_COMMAND = "set search_path to"
29+
POSTGRES_FETCH_SIZE = 1000
30+
31+
class << self
32+
def accept?(type)
33+
type == 'postgresql'
34+
end
35+
end
36+
37+
def initialize(options = {})
38+
raise("Data Source needs a client to Postgres to be able to query the storage but 'postgresql_client' is empty.") unless options['postgresql_client']
39+
40+
if options['postgresql_client']['connection'].is_a?(Hash)
41+
@database = options['postgresql_client']['connection']['database']
42+
@schema = options['postgresql_client']['connection']['schema'] || 'public'
43+
@authentication = options['postgresql_client']['connection']['authentication']
44+
@ssl_mode = options['postgresql_client']['connection']['sslMode']
45+
raise "SSL Mode should be prefer, require and verify-full" unless @ssl_mode == 'prefer' || @ssl_mode == 'require' || @ssl_mode == 'verify-full'
46+
47+
@url = build_url(options['postgresql_client']['connection']['url'])
48+
else
49+
raise('Missing connection info for Postgres client')
50+
end
51+
52+
Java.org.postgresql.Driver
53+
end
54+
55+
def realize_query(query, _params)
56+
GoodData.gd_logger.info("Realize SQL query: type=postgresql status=started")
57+
58+
connect
59+
filename = "#{SecureRandom.urlsafe_base64(6)}_#{Time.now.to_i}.csv"
60+
measure = Benchmark.measure do
61+
statement = @connection.create_statement
62+
statement.set_fetch_size(POSTGRES_FETCH_SIZE)
63+
has_result = statement.execute(query)
64+
if has_result
65+
result = statement.get_result_set
66+
metadata = result.get_meta_data
67+
col_count = metadata.column_count
68+
CSV.open(filename, 'wb') do |csv|
69+
csv << Array(1..col_count).map { |i| metadata.get_column_name(i) } # build the header
70+
csv << Array(1..col_count).map { |i| result.get_string(i)&.to_s } while result.next
71+
end
72+
end
73+
end
74+
GoodData.gd_logger.info("Realize SQL query: type=postgresql status=finished duration=#{measure.real}")
75+
filename
76+
ensure
77+
@connection&.close
78+
@connection = nil
79+
end
80+
81+
def connect
82+
GoodData.logger.info "Setting up connection to Postgresql #{@url}"
83+
84+
prop = java.util.Properties.new
85+
prop.setProperty('user', @authentication['basic']['userName'])
86+
prop.setProperty('password', @authentication['basic']['password'])
87+
prop.setProperty('schema', @schema)
88+
89+
@connection = java.sql.DriverManager.getConnection(@url, prop)
90+
statement = @connection.create_statement
91+
statement.execute("#{POSTGRES_SET_SCHEMA_COMMAND} #{@schema}")
92+
@connection.set_auto_commit(false)
93+
end
94+
95+
def build_url(url)
96+
matches = url.scan(JDBC_POSTGRES_PATTERN)
97+
raise 'Cannot reach the url' unless matches
98+
99+
host = matches[0][0]
100+
port = matches[0][2]&.to_i || POSTGRES_DEFAULT_PORT
101+
raise "Custom port #{port} is not supported. Remove it or use the default port '5432'" if POSTGRES_DEFAULT_PORT != port
102+
103+
"#{JDBC_POSTGRES_PROTOCOL}#{host}:#{port}/#{@database}?sslmode=#{@ssl_mode}#{VERIFY_FULL == @ssl_mode ? SSL_JAVA_FACTORY : ''}"
104+
end
105+
end
106+
end
107+
end

lib/gooddata/helpers/data_helper.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,8 @@ def realize(params = {})
4444
realize_link
4545
when 's3'
4646
realize_s3(params)
47-
when 'redshift', 'snowflake', 'bigquery'
47+
when 'redshift', 'snowflake', 'bigquery', 'postgresql'
4848
raise GoodData::InvalidEnvError, "DataSource does not support type \"#{source}\" on the platform #{RUBY_PLATFORM}" unless RUBY_PLATFORM =~ /java/
49-
5049
require_relative '../cloud_resources/cloud_resources'
5150
realize_cloud_resource(source, params)
5251
else

0 commit comments

Comments
 (0)