quill-sql 0.1.3 → 0.1.4
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 +4 -4
- data/lib/db/clickhouse.rb +4 -15
- data/lib/db/db_helper.rb +20 -1
- data/lib/db/postgres.rb +177 -0
- data/lib/quill-sql.rb +9 -6
- data/lib/utils/tenants.rb +4 -4
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 76485b20728b04f2100bcf4d6a35b4308fd7c14be60f9ffb992a012e97b083c2
|
4
|
+
data.tar.gz: 3fb5e7d7acda9a4011c7e23f9a778eb65d4dc020418b9d13259ca0cfaef69cb6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e92f9001384b9c4313c6d21f0b41a2a37f14b30542775d994c591c05196b49cdf57f219370e6d58d72bbb3959442c3e7715b89c297b09073b1c58c68ebeecf53
|
7
|
+
data.tar.gz: b7cea1082503f99ecb4f5ae2144b66d9706fa2994fd33e6bf442a67145c748bcc9866c67cd8b698405bf1d1539affd0e51ecdafcfabea7705c921d696488ed3d
|
data/lib/db/clickhouse.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'uri'
|
2
2
|
require 'json'
|
3
3
|
require 'click_house'
|
4
|
+
require 'active_support/inflector'
|
4
5
|
|
5
6
|
module ClickHouseHelper
|
6
7
|
# Constants
|
@@ -242,7 +243,7 @@ module ClickHouseHelper
|
|
242
243
|
end
|
243
244
|
|
244
245
|
def get_foreign_keys_clickhouse(client, schema_name, table_name, primary_key)
|
245
|
-
depluralized_table_name =
|
246
|
+
depluralized_table_name = table_name.singularize
|
246
247
|
|
247
248
|
sql = <<~SQL
|
248
249
|
SELECT column_name
|
@@ -251,7 +252,7 @@ module ClickHouseHelper
|
|
251
252
|
AND table_name != '#{table_name}'
|
252
253
|
AND (column_name = '#{primary_key}'
|
253
254
|
OR column_name = '#{depluralized_table_name}_#{primary_key}'
|
254
|
-
OR column_name = '#{depluralized_table_name}#{capitalize
|
255
|
+
OR column_name = '#{depluralized_table_name}#{primary_key.capitalize}')
|
255
256
|
SQL
|
256
257
|
|
257
258
|
results = run_query_clickhouse(sql, client)
|
@@ -270,7 +271,7 @@ module ClickHouseHelper
|
|
270
271
|
OR column_name LIKE '%\_id'
|
271
272
|
OR column_name LIKE '%Id'
|
272
273
|
OR column_name LIKE '%\_#{primary_key}'
|
273
|
-
OR column_name LIKE '%#{capitalize
|
274
|
+
OR column_name LIKE '%#{primary_key.capitalize}')
|
274
275
|
SQL
|
275
276
|
|
276
277
|
results = run_query_clickhouse(sql, client)
|
@@ -317,17 +318,5 @@ module ClickHouseHelper
|
|
317
318
|
password: parsed.password || ''
|
318
319
|
}
|
319
320
|
end
|
320
|
-
|
321
|
-
private
|
322
|
-
|
323
|
-
def capitalize(str)
|
324
|
-
str.capitalize
|
325
|
-
end
|
326
|
-
|
327
|
-
def depluralize(str)
|
328
|
-
# Simple depluralization - you might want to use a proper library like ActiveSupport
|
329
|
-
return str[0..-2] if str.end_with?('s')
|
330
|
-
str
|
331
|
-
end
|
332
321
|
end
|
333
322
|
end
|
data/lib/db/db_helper.rb
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
require 'json'
|
2
2
|
require 'uri'
|
3
3
|
require_relative 'clickhouse'
|
4
|
+
require_relative 'postgres'
|
4
5
|
|
5
6
|
module DatabaseHelper
|
6
|
-
SUPPORTED_DATABASES = ['clickhouse'].freeze
|
7
|
+
SUPPORTED_DATABASES = ['clickhouse', 'postgresql'].freeze
|
7
8
|
|
8
9
|
class QuillQueryResults
|
9
10
|
attr_reader :fields, :rows
|
@@ -20,6 +21,8 @@ module DatabaseHelper
|
|
20
21
|
case database_type.downcase
|
21
22
|
when 'clickhouse'
|
22
23
|
ClickHouseHelper.format_clickhouse_config(connection_string)
|
24
|
+
when 'postgresql'
|
25
|
+
PostgresHelper.format_postgres_config(connection_string)
|
23
26
|
else
|
24
27
|
raise DatabaseError, "Invalid database type: #{database_type}"
|
25
28
|
end
|
@@ -29,6 +32,8 @@ module DatabaseHelper
|
|
29
32
|
case database_type.downcase
|
30
33
|
when 'clickhouse'
|
31
34
|
ClickHouseHelper.connect_to_clickhouse(config)
|
35
|
+
when 'postgresql'
|
36
|
+
PostgresHelper.connect_to_postgres(config)
|
32
37
|
else
|
33
38
|
raise DatabaseError, "Invalid database type: #{database_type}"
|
34
39
|
end
|
@@ -50,6 +55,8 @@ module DatabaseHelper
|
|
50
55
|
case database_type.downcase
|
51
56
|
when 'clickhouse'
|
52
57
|
ClickHouseHelper.run_query_clickhouse(sql, connection)
|
58
|
+
when 'postgresql'
|
59
|
+
PostgresHelper.run_query_postgres(sql, connection)
|
53
60
|
else
|
54
61
|
raise DatabaseError, "Invalid database type: #{database_type}"
|
55
62
|
end
|
@@ -65,6 +72,8 @@ module DatabaseHelper
|
|
65
72
|
case database_type.downcase
|
66
73
|
when 'clickhouse'
|
67
74
|
ClickHouseHelper.disconnect_from_clickhouse(database)
|
75
|
+
when 'postgresql'
|
76
|
+
PostgresHelper.disconnect_from_postgres(database)
|
68
77
|
end
|
69
78
|
end
|
70
79
|
|
@@ -72,6 +81,8 @@ module DatabaseHelper
|
|
72
81
|
case database_type.downcase
|
73
82
|
when 'clickhouse'
|
74
83
|
ClickHouseHelper.get_schemas_clickhouse(connection)
|
84
|
+
when 'postgresql'
|
85
|
+
PostgresHelper.get_schemas_postgres(connection)
|
75
86
|
else
|
76
87
|
raise DatabaseError, "Invalid database type: #{database_type}"
|
77
88
|
end
|
@@ -81,6 +92,8 @@ module DatabaseHelper
|
|
81
92
|
case database_type.downcase
|
82
93
|
when 'clickhouse'
|
83
94
|
ClickHouseHelper.get_tables_by_schema_clickhouse(connection, schema_name)
|
95
|
+
when 'postgresql'
|
96
|
+
PostgresHelper.get_tables_by_schema_postgres(connection, schema_name)
|
84
97
|
else
|
85
98
|
raise DatabaseError, "Invalid database type: #{database_type}"
|
86
99
|
end
|
@@ -90,6 +103,8 @@ module DatabaseHelper
|
|
90
103
|
case database_type.downcase
|
91
104
|
when 'clickhouse'
|
92
105
|
ClickHouseHelper.get_columns_by_table_clickhouse(connection, schema_name, table_name)
|
106
|
+
when 'postgresql'
|
107
|
+
PostgresHelper.get_columns_by_table_postgres(connection, schema_name, table_name)
|
93
108
|
else
|
94
109
|
raise DatabaseError, "Invalid database type: #{database_type}"
|
95
110
|
end
|
@@ -99,6 +114,8 @@ module DatabaseHelper
|
|
99
114
|
case database_type.downcase
|
100
115
|
when 'clickhouse'
|
101
116
|
ClickHouseHelper.get_foreign_keys_clickhouse(connection, schema_name, table_name, primary_key)
|
117
|
+
when 'postgresql'
|
118
|
+
PostgresHelper.get_foreign_keys_postgres(connection, schema_name, table_name, primary_key)
|
102
119
|
else
|
103
120
|
raise DatabaseError, "Invalid database type: #{database_type}"
|
104
121
|
end
|
@@ -108,6 +125,8 @@ module DatabaseHelper
|
|
108
125
|
case database_type.downcase
|
109
126
|
when 'clickhouse'
|
110
127
|
ClickHouseHelper.get_schema_column_info_clickhouse(connection, schema_name, tables)
|
128
|
+
when 'postgresql'
|
129
|
+
PostgresHelper.get_schema_column_info_postgres(connection, schema_name, tables)
|
111
130
|
else
|
112
131
|
raise DatabaseError, "Invalid database type: #{database_type}"
|
113
132
|
end
|
data/lib/db/postgres.rb
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'pg'
|
2
|
+
require 'json'
|
3
|
+
require 'uri'
|
4
|
+
require 'active_support/inflector'
|
5
|
+
|
6
|
+
module PostgresHelper
|
7
|
+
class << self
|
8
|
+
def connect_to_postgres(config)
|
9
|
+
conn = PG.connect(
|
10
|
+
host: config[:host],
|
11
|
+
port: config[:port],
|
12
|
+
dbname: config[:database],
|
13
|
+
user: config[:username],
|
14
|
+
password: config[:password],
|
15
|
+
sslmode: config[:sslmode] || 'prefer',
|
16
|
+
gssencmode: 'disable', # Disable GSSAPI encryption
|
17
|
+
krbsrvname: nil, # Disable Kerberos service name
|
18
|
+
target_session_attrs: 'read-write'
|
19
|
+
)
|
20
|
+
conn
|
21
|
+
end
|
22
|
+
|
23
|
+
def disconnect_from_postgres(client)
|
24
|
+
client.close if client.respond_to?(:close)
|
25
|
+
end
|
26
|
+
|
27
|
+
def run_query_postgres(sql, client)
|
28
|
+
result = client.exec(sql)
|
29
|
+
|
30
|
+
fields = result.fields.map do |field|
|
31
|
+
{
|
32
|
+
name: field,
|
33
|
+
dataTypeID: result.ftype(result.fields.index(field))
|
34
|
+
}
|
35
|
+
end
|
36
|
+
|
37
|
+
{
|
38
|
+
fields: fields,
|
39
|
+
rows: result.values.map { |row| result.fields.zip(row).to_h }
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_schemas_postgres(client)
|
44
|
+
sql = <<~SQL
|
45
|
+
SELECT schema_name
|
46
|
+
FROM information_schema.schemata
|
47
|
+
WHERE schema_name NOT LIKE 'pg_%'
|
48
|
+
AND schema_name != 'information_schema'
|
49
|
+
SQL
|
50
|
+
|
51
|
+
results = run_query_postgres(sql, client)
|
52
|
+
results[:rows].map { |row| row['schema_name'] }
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_tables_by_schema_postgres(client, schema_names)
|
56
|
+
all_tables = schema_names.flat_map do |schema|
|
57
|
+
sql = <<~SQL
|
58
|
+
SELECT table_name, table_schema
|
59
|
+
FROM information_schema.tables
|
60
|
+
WHERE table_schema = '#{schema}'
|
61
|
+
AND table_type = 'BASE TABLE'
|
62
|
+
SQL
|
63
|
+
|
64
|
+
results = run_query_postgres(sql, client)
|
65
|
+
results[:rows].map do |row|
|
66
|
+
{
|
67
|
+
tableName: row['table_name'],
|
68
|
+
schemaName: row['table_schema']
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
all_tables
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_columns_by_table_postgres(client, schema_name, table_name)
|
77
|
+
sql = <<~SQL
|
78
|
+
SELECT column_name
|
79
|
+
FROM information_schema.columns
|
80
|
+
WHERE table_schema = '#{schema_name}'
|
81
|
+
AND table_name = '#{table_name}'
|
82
|
+
ORDER BY ordinal_position
|
83
|
+
SQL
|
84
|
+
|
85
|
+
results = run_query_postgres(sql, client)
|
86
|
+
results[:rows].map { |row| row['column_name'] }
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_foreign_keys_postgres(client, schema_name, table_name, primary_key)
|
90
|
+
depluralized_table_name = table_name.singularize
|
91
|
+
|
92
|
+
sql = <<~SQL
|
93
|
+
SELECT column_name
|
94
|
+
FROM information_schema.columns
|
95
|
+
WHERE table_schema = '#{schema_name}'
|
96
|
+
AND table_name != '#{table_name}'
|
97
|
+
AND (column_name = '#{primary_key}'
|
98
|
+
OR column_name = '#{depluralized_table_name}_#{primary_key}'
|
99
|
+
OR column_name = '#{depluralized_table_name}#{primary_key.capitalize}')
|
100
|
+
SQL
|
101
|
+
|
102
|
+
results = run_query_postgres(sql, client)
|
103
|
+
foreign_keys = results[:rows].map { |key| key['column_name'] }
|
104
|
+
|
105
|
+
foreign_keys = foreign_keys.reject { |key| ['id', '_id_'].include?(key) }
|
106
|
+
foreign_keys = foreign_keys.uniq
|
107
|
+
|
108
|
+
if foreign_keys.empty?
|
109
|
+
sql = <<~SQL
|
110
|
+
SELECT column_name
|
111
|
+
FROM information_schema.columns
|
112
|
+
WHERE table_schema = '#{schema_name}'
|
113
|
+
AND table_name != '#{table_name}'
|
114
|
+
AND (column_name LIKE '#{table_name}%'
|
115
|
+
OR column_name LIKE '%\\_id'
|
116
|
+
OR column_name LIKE '%Id'
|
117
|
+
OR column_name LIKE '%\\_#{primary_key}'
|
118
|
+
OR column_name LIKE '%#{primary_key.capitalize}')
|
119
|
+
SQL
|
120
|
+
|
121
|
+
results = run_query_postgres(sql, client)
|
122
|
+
foreign_keys = results[:rows].map { |key| key['column_name'] }.uniq
|
123
|
+
end
|
124
|
+
|
125
|
+
foreign_keys
|
126
|
+
end
|
127
|
+
|
128
|
+
def get_schema_column_info_postgres(client, schema_name, table_names)
|
129
|
+
table_names.map do |table_name|
|
130
|
+
query = <<~SQL
|
131
|
+
SELECT
|
132
|
+
column_name,
|
133
|
+
udt_name as field_type,
|
134
|
+
ordinal_position as sort_number
|
135
|
+
FROM information_schema.columns
|
136
|
+
WHERE table_schema = '#{table_name[:schemaName]}'
|
137
|
+
AND table_name = '#{table_name[:tableName]}'
|
138
|
+
ORDER BY sort_number
|
139
|
+
SQL
|
140
|
+
|
141
|
+
results = run_query_postgres(query, client)
|
142
|
+
{
|
143
|
+
tableName: "#{table_name[:schemaName]}.#{table_name[:tableName]}",
|
144
|
+
displayName: "#{table_name[:schemaName]}.#{table_name[:tableName]}",
|
145
|
+
columns: results[:rows].map do |row|
|
146
|
+
{
|
147
|
+
columnName: row['column_name'],
|
148
|
+
displayName: row['column_name'],
|
149
|
+
dataTypeID: get_pg_type_oid(row['field_type']),
|
150
|
+
fieldType: row['field_type']
|
151
|
+
}
|
152
|
+
end
|
153
|
+
}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def format_postgres_config(connection_string)
|
158
|
+
parsed = URI.parse(connection_string)
|
159
|
+
|
160
|
+
{
|
161
|
+
host: parsed.host,
|
162
|
+
port: parsed.port || 5432,
|
163
|
+
database: parsed.path[1..-1],
|
164
|
+
username: parsed.user || 'postgres',
|
165
|
+
password: parsed.password || '',
|
166
|
+
sslmode: parsed.query&.split('&')&.find { |param| param.start_with?('sslmode=') }&.split('=')&.last || 'prefer'
|
167
|
+
}
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
def get_pg_type_oid(type_name)
|
173
|
+
type = PG_TYPES.find { |t| t[:typname] == type_name }&.dig(:oid)
|
174
|
+
type || 1043 # Default to varchar if type not found
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
data/lib/quill-sql.rb
CHANGED
@@ -84,12 +84,11 @@ class Quill
|
|
84
84
|
# If the task requires flags to be synthesized from tenants
|
85
85
|
if FLAG_TASKS.include?(metadata[:task]) &&
|
86
86
|
tenants&.first != ALL_TENANTS &&
|
87
|
-
tenants&.first != SINGLE_TENANT
|
88
|
-
(metadata[:task] != 'filter-options' ||
|
89
|
-
!metadata[:reportId])
|
87
|
+
tenants&.first != SINGLE_TENANT
|
90
88
|
|
91
89
|
response = post_quill('tenant-mapped-flags', {
|
92
90
|
reportId: metadata[:reportId] || metadata[:dashboardItemId],
|
91
|
+
clientId: metadata[:clientId],
|
93
92
|
dashboardName: metadata[:name],
|
94
93
|
tenants: tenants.map { |tenant| TenantUtils.deep_camelize_keys(tenant) },
|
95
94
|
flags: flags&.map { |flag| TenantUtils.deep_camelize_keys(flag) }
|
@@ -140,14 +139,18 @@ class Quill
|
|
140
139
|
}
|
141
140
|
end
|
142
141
|
|
143
|
-
|
142
|
+
sdk_filters = filters&.map { |filter| FilterUtils.convert_custom_filter(filter) }
|
143
|
+
|
144
|
+
payload = {
|
144
145
|
**metadata,
|
145
|
-
sdkFilters: filters&.map { |filter| FilterUtils.convert_custom_filter(filter) },
|
146
146
|
**pre_query_results,
|
147
147
|
tenants: tenants.map { |tenant| TenantUtils.deep_camelize_keys(tenant) },
|
148
148
|
flags: tenant_flags&.map { |flag| TenantUtils.deep_camelize_keys(flag) },
|
149
149
|
viewQuery: metadata[:preQueries]&.first
|
150
|
-
}
|
150
|
+
}
|
151
|
+
payload[:sdkFilters] = sdk_filters if sdk_filters # So it's undefined instead of nil
|
152
|
+
|
153
|
+
response = post_quill(metadata[:task], payload)
|
151
154
|
|
152
155
|
return {
|
153
156
|
status: "error",
|
data/lib/utils/tenants.rb
CHANGED
@@ -2,8 +2,8 @@ module TenantUtils
|
|
2
2
|
def self.extract_tenant_ids(tenants)
|
3
3
|
if tenants[0].is_a?(String) || tenants[0].is_a?(Numeric)
|
4
4
|
tenants
|
5
|
-
elsif tenants[0].is_a?(Hash) && (tenants[0].key?('
|
6
|
-
tenants[0]['
|
5
|
+
elsif tenants[0].is_a?(Hash) && (tenants[0].key?('tenantIds') || tenants[0].key?(:tenantIds))
|
6
|
+
tenants[0]['tenantIds'] || tenants[0][:tenantIds]
|
7
7
|
else
|
8
8
|
raise 'Invalid format for tenants'
|
9
9
|
end
|
@@ -12,8 +12,8 @@ module TenantUtils
|
|
12
12
|
def self.extract_tenant_field(tenants, dashboard_owner)
|
13
13
|
if tenants[0].is_a?(String) || tenants[0].is_a?(Numeric)
|
14
14
|
dashboard_owner
|
15
|
-
elsif tenants[0].is_a?(Hash) && (tenants[0].key?('
|
16
|
-
tenants[0]['
|
15
|
+
elsif tenants[0].is_a?(Hash) && (tenants[0].key?('tenantField') || tenants[0].key?(:tenantField))
|
16
|
+
tenants[0]['tenantField'] || tenants[0][:tenantField]
|
17
17
|
else
|
18
18
|
raise 'Invalid format for tenants'
|
19
19
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: quill-sql
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Shawn Magee, Albert Yan
|
8
8
|
- Sam Bishop
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-04-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: json
|
@@ -135,6 +135,7 @@ files:
|
|
135
135
|
- lib/db/cached_connection.rb
|
136
136
|
- lib/db/clickhouse.rb
|
137
137
|
- lib/db/db_helper.rb
|
138
|
+
- lib/db/postgres.rb
|
138
139
|
- lib/models/filters.rb
|
139
140
|
- lib/quill-sql.rb
|
140
141
|
- lib/utils/run_query_processes.rb
|