dwh 0.1.0

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.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +36 -0
  3. data/CHANGELOG.md +5 -0
  4. data/LICENSE +21 -0
  5. data/README.md +130 -0
  6. data/Rakefile +42 -0
  7. data/docs/DWH/Adapters/Adapter.html +3053 -0
  8. data/docs/DWH/Adapters/Athena.html +1704 -0
  9. data/docs/DWH/Adapters/Boolean.html +121 -0
  10. data/docs/DWH/Adapters/Druid.html +1626 -0
  11. data/docs/DWH/Adapters/DuckDb.html +2012 -0
  12. data/docs/DWH/Adapters/MySql.html +1704 -0
  13. data/docs/DWH/Adapters/OpenAuthorizable/ClassMethods.html +265 -0
  14. data/docs/DWH/Adapters/OpenAuthorizable.html +1102 -0
  15. data/docs/DWH/Adapters/Postgres.html +2000 -0
  16. data/docs/DWH/Adapters/Snowflake.html +1662 -0
  17. data/docs/DWH/Adapters/SqlServer.html +2084 -0
  18. data/docs/DWH/Adapters/Trino.html +1835 -0
  19. data/docs/DWH/Adapters.html +129 -0
  20. data/docs/DWH/AuthenticationError.html +142 -0
  21. data/docs/DWH/Behaviors.html +767 -0
  22. data/docs/DWH/Capabilities.html +748 -0
  23. data/docs/DWH/Column.html +1115 -0
  24. data/docs/DWH/ConfigError.html +143 -0
  25. data/docs/DWH/ConnectionError.html +143 -0
  26. data/docs/DWH/DWHError.html +138 -0
  27. data/docs/DWH/ExecutionError.html +143 -0
  28. data/docs/DWH/Factory.html +1133 -0
  29. data/docs/DWH/Functions/Arrays.html +505 -0
  30. data/docs/DWH/Functions/Dates.html +1644 -0
  31. data/docs/DWH/Functions/ExtractDatePart.html +804 -0
  32. data/docs/DWH/Functions/Nulls.html +377 -0
  33. data/docs/DWH/Functions.html +846 -0
  34. data/docs/DWH/Logger.html +258 -0
  35. data/docs/DWH/OAuthError.html +138 -0
  36. data/docs/DWH/Settings.html +658 -0
  37. data/docs/DWH/StreamingStats.html +804 -0
  38. data/docs/DWH/Table.html +1260 -0
  39. data/docs/DWH/TableStats.html +583 -0
  40. data/docs/DWH/TokenExpiredError.html +142 -0
  41. data/docs/DWH/UnsupportedCapability.html +135 -0
  42. data/docs/DWH.html +220 -0
  43. data/docs/_index.html +471 -0
  44. data/docs/class_list.html +54 -0
  45. data/docs/css/common.css +1 -0
  46. data/docs/css/full_list.css +58 -0
  47. data/docs/css/style.css +503 -0
  48. data/docs/file.README.html +210 -0
  49. data/docs/file.adapters.html +514 -0
  50. data/docs/file.creating-adapters.html +497 -0
  51. data/docs/file.getting-started.html +288 -0
  52. data/docs/file.usage.html +446 -0
  53. data/docs/file_list.html +79 -0
  54. data/docs/frames.html +22 -0
  55. data/docs/guides/adapters.md +445 -0
  56. data/docs/guides/creating-adapters.md +430 -0
  57. data/docs/guides/getting-started.md +225 -0
  58. data/docs/guides/usage.md +378 -0
  59. data/docs/index.html +210 -0
  60. data/docs/js/app.js +344 -0
  61. data/docs/js/full_list.js +242 -0
  62. data/docs/js/jquery.js +4 -0
  63. data/docs/method_list.html +2038 -0
  64. data/docs/top-level-namespace.html +110 -0
  65. data/lib/dwh/adapters/athena.rb +359 -0
  66. data/lib/dwh/adapters/druid.rb +267 -0
  67. data/lib/dwh/adapters/duck_db.rb +235 -0
  68. data/lib/dwh/adapters/my_sql.rb +235 -0
  69. data/lib/dwh/adapters/open_authorizable.rb +215 -0
  70. data/lib/dwh/adapters/postgres.rb +250 -0
  71. data/lib/dwh/adapters/snowflake.rb +489 -0
  72. data/lib/dwh/adapters/sql_server.rb +257 -0
  73. data/lib/dwh/adapters/trino.rb +213 -0
  74. data/lib/dwh/adapters.rb +363 -0
  75. data/lib/dwh/behaviors.rb +67 -0
  76. data/lib/dwh/capabilities.rb +39 -0
  77. data/lib/dwh/column.rb +79 -0
  78. data/lib/dwh/errors.rb +29 -0
  79. data/lib/dwh/factory.rb +125 -0
  80. data/lib/dwh/functions/arrays.rb +42 -0
  81. data/lib/dwh/functions/dates.rb +162 -0
  82. data/lib/dwh/functions/extract_date_part.rb +70 -0
  83. data/lib/dwh/functions/nulls.rb +31 -0
  84. data/lib/dwh/functions.rb +86 -0
  85. data/lib/dwh/logger.rb +50 -0
  86. data/lib/dwh/settings/athena.yml +77 -0
  87. data/lib/dwh/settings/base.yml +81 -0
  88. data/lib/dwh/settings/databricks.yml +51 -0
  89. data/lib/dwh/settings/druid.yml +59 -0
  90. data/lib/dwh/settings/duckdb.yml +44 -0
  91. data/lib/dwh/settings/mysql.yml +67 -0
  92. data/lib/dwh/settings/postgres.yml +30 -0
  93. data/lib/dwh/settings/redshift.yml +52 -0
  94. data/lib/dwh/settings/snowflake.yml +45 -0
  95. data/lib/dwh/settings/sqlserver.yml +80 -0
  96. data/lib/dwh/settings/trino.yml +77 -0
  97. data/lib/dwh/settings.rb +79 -0
  98. data/lib/dwh/streaming_stats.rb +69 -0
  99. data/lib/dwh/table.rb +105 -0
  100. data/lib/dwh/table_stats.rb +51 -0
  101. data/lib/dwh/version.rb +5 -0
  102. data/lib/dwh.rb +54 -0
  103. data/sig/dwh.rbs +4 -0
  104. metadata +231 -0
@@ -0,0 +1,497 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ File: Creating Custom Adapters
8
+
9
+ &mdash; Documentation by YARD 0.9.37
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" />
16
+
17
+ <script type="text/javascript">
18
+ pathId = "creating-adapters";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="file_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index</a> &raquo;
40
+ <span class="title">File: Creating Custom Adapters</span>
41
+
42
+ </div>
43
+
44
+ <div id="search">
45
+
46
+ <a class="full_list_link" id="class_list_link"
47
+ href="class_list.html">
48
+
49
+ <svg width="24" height="24">
50
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
51
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
52
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
53
+ </svg>
54
+ </a>
55
+
56
+ </div>
57
+ <div class="clear"></div>
58
+ </div>
59
+
60
+ <div id="content"><div id='filecontents'><h1 id="creating-custom-adapters">Creating Custom Adapters</h1>
61
+
62
+ <p>The whole point of this library is to make adding a new database integration easy. With a few steps you can create your own adapter. If its generic, please contribute back to the project via PR.</p>
63
+
64
+ <p>This guide walks you through creating your own custom database adapter for DWH. Creating a new adapter involves extending the base adapter class, implementing required methods, and optionally creating custom settings.</p>
65
+
66
+ <h2 id="understanding-dwh-architecture">Understanding DWH Architecture</h2>
67
+
68
+ <p>DWH adapters have a simple, focused architecture:</p>
69
+
70
+ <ul>
71
+ <li><strong>5 Core Methods</strong>: Every adapter must implement 5 essential methods</li>
72
+ <li><strong>YAML Settings</strong>: Database-specific behavior controlled by YAML configuration</li>
73
+ <li><strong>Configuration Validation</strong>: Automatic validation of connection parameters</li>
74
+ <li><strong>Function Translation</strong>: SQL functions automatically translated to database-specific syntax</li>
75
+ </ul>
76
+
77
+ <h2 id="minimal-adapter-example">Minimal Adapter Example</h2>
78
+
79
+ <p>Here’s a minimal adapter implementation:</p>
80
+
81
+ <p>```ruby
82
+ module DWH
83
+ module Adapters
84
+ class MyCustomAdapter &lt; Adapter
85
+ # Define required configuration parameters
86
+ config :host, String, required: true, message: ‘server host ip address or domain name’
87
+ config :port, Integer, required: false, default: 1234, message: ‘port to connect to’
88
+ config :database, String, required: true, message: ‘name of database to connect to’
89
+ config :username, String, required: true, message: ‘connection username’
90
+ config :password, String, required: false, default: nil, message: ‘connection password’</p>
91
+
92
+ <pre class="code ruby"><code class="ruby"> # Implement required methods
93
+ def connection
94
+ # Return your database connection object
95
+ # This is cached, so implement connection reuse here
96
+ @connection ||= create_connection
97
+ end
98
+
99
+ def tables(catalog: nil, schema: nil)
100
+ # Return array of DWH::Table objects
101
+ # Use catalog/schema for filtering if supported
102
+ end
103
+
104
+ def metadata(table_name, catalog: nil, schema: nil)
105
+ # Return single DWH::Table object with column information
106
+ end
107
+
108
+ def stats(table_name, date_column: nil, catalog: nil, schema: nil)
109
+ # Return DWH::TableStats object with row counts and date ranges
110
+ end
111
+
112
+ def execute(sql, format: :array, retries: 0)
113
+ # Execute SQL and return results in specified format
114
+ # Formats: :array, :object, :csv, :native
115
+ end
116
+
117
+ def execute_stream(sql, io, stats: nil)
118
+ # Execute SQL and stream results directly to IO object
119
+ end
120
+
121
+ def stream(sql, &amp;block)
122
+ # Execute SQL and yield chunks to block
123
+ end
124
+
125
+ private
126
+
127
+ def create_connection
128
+ # Your database-specific connection logic
129
+ MyDatabaseClient.connect(
130
+ host: config[:host],
131
+ port: config[:port],
132
+ database: config[:database],
133
+ username: config[:username],
134
+ password: config[:password]
135
+ )
136
+ end
137
+ end end end
138
+ </code></pre>
139
+
140
+ <h1 id="register-your-adapter">Register your adapter</h1>
141
+ <p>DWH.register(:mycustom, DWH::Adapters::MyCustomAdapter)
142
+ ```</p>
143
+
144
+ <h2 id="step-by-step-implementation">Step-by-Step Implementation</h2>
145
+
146
+ <h3 id="define-configuration-parameters">1. Define Configuration Parameters</h3>
147
+
148
+ <p>Use the <code>config</code> class method to define connection parameters:</p>
149
+
150
+ <p>```ruby
151
+ class MyCustomAdapter &lt; Adapter
152
+ # Required parameters
153
+ config :host, String, required: true, message: ‘server host ip address or domain name’
154
+ config :database, String, required: true, message: ‘name of database to connect to’</p>
155
+
156
+ <p># Optional parameters with defaults
157
+ config :port, Integer, required: false, default: 5432, message: ‘port to connect to’
158
+ config :timeout, Integer, required: false, default: 30, message: ‘connection timeout’</p>
159
+
160
+ <p># Boolean parameters
161
+ config :ssl, Boolean, required: false, default: false, message: ‘use ssl connection’</p>
162
+
163
+ <p># Parameters with allowed values
164
+ config :auth_type, String, required: false, default: ‘basic’,
165
+ message: ‘authentication type’, allowed: %w[basic oauth token]
166
+ end
167
+ ```</p>
168
+
169
+ <h3 id="implement-connection-management">2. Implement Connection Management</h3>
170
+
171
+ <p>```ruby
172
+ def connection
173
+ return @connection if @connection &amp;&amp; connection_valid?</p>
174
+
175
+ <p>@connection = create_connection
176
+ end</p>
177
+
178
+ <p>private</p>
179
+
180
+ <p>def create_connection
181
+ # Example for HTTP-based database
182
+ Faraday.new(
183
+ url: “#protocol://#config[:host]:#config[:port]”,
184
+ headers: build_headers,
185
+ request: {
186
+ timeout: config[:timeout]
187
+ }
188
+ )
189
+ end</p>
190
+
191
+ <p>def build_headers
192
+ headers = { ‘Content-Type’ =&gt; ‘application/json’ }
193
+ headers[‘Authorization’] = “Bearer #config[:token]” if config[:token]
194
+ headers
195
+ end</p>
196
+
197
+ <p>def connection_valid?
198
+ # Implement connection health check
199
+ @connection&amp;.get(‘/health’)&amp;.success?
200
+ rescue
201
+ false
202
+ end
203
+ ```</p>
204
+
205
+ <h3 id="implement-table-discovery">3. Implement Table Discovery</h3>
206
+
207
+ <p>```ruby
208
+ def tables(catalog: nil, schema: nil)
209
+ query = build_tables_query(catalog: catalog, schema: schema)
210
+ results = execute(query, format: :array)</p>
211
+
212
+ <p>results.map do |row|
213
+ DWH::Table.new(
214
+ physical_name: row[0],
215
+ schema: row[1] || ‘default’,
216
+ catalog: row[2],
217
+ table_type: row[3] || ‘TABLE’
218
+ )
219
+ end
220
+ end</p>
221
+
222
+ <p>private</p>
223
+
224
+ <p>def build_tables_query(catalog: nil, schema: nil)
225
+ query = “SHOW TABLES”</p>
226
+
227
+ <p>conditions = []
228
+ conditions « “FROM #catalog” if catalog
229
+ conditions « “LIKE ‘#schema.%’” if schema</p>
230
+
231
+ <p>query += “ #‘)” unless conditions.empty?
232
+ query
233
+ end
234
+ ```</p>
235
+
236
+ <h3 id="implement-metadata-extraction">4. Implement Metadata Extraction</h3>
237
+
238
+ <p>```ruby
239
+ def metadata(table_name, catalog: nil, schema: nil)
240
+ # Parse table name if it includes schema/catalog
241
+ parsed = parse_table_name(table_name, catalog: catalog, schema: schema)</p>
242
+
243
+ <p>query = build_describe_query(parsed[:table], parsed[:schema], parsed[:catalog])
244
+ results = execute(query, format: :array)</p>
245
+
246
+ <p>columns = results.map do |row|
247
+ DWH::Column.new(
248
+ name: row[0],
249
+ data_type: row[1],
250
+ normalized_data_type: normalize_data_type(row[1]),
251
+ nullable: row[2] != ‘NO’,
252
+ default_value: row[3],
253
+ character_maximum_length: row[4],
254
+ numeric_precision: row[5],
255
+ numeric_scale: row[6]
256
+ )
257
+ end</p>
258
+
259
+ <p>DWH::Table.new(
260
+ physical_name: parsed[:table],
261
+ schema: parsed[:schema],
262
+ catalog: parsed[:catalog],
263
+ columns: columns
264
+ )
265
+ end
266
+ ```</p>
267
+
268
+ <h3 id="implement-statistics-collection">5. Implement Statistics Collection</h3>
269
+
270
+ <p>```ruby
271
+ def stats(table_name, date_column: nil, catalog: nil, schema: nil)
272
+ parsed = parse_table_name(table_name, catalog: catalog, schema: schema)
273
+ full_table_name = build_full_table_name(parsed)</p>
274
+
275
+ <p># Get row count
276
+ count_query = “SELECT COUNT(*) FROM #full_table_name”
277
+ row_count = execute(count_query, format: :array).first.first</p>
278
+
279
+ <p># Get date range if date column provided
280
+ date_start = date_end = nil
281
+ if date_column
282
+ date_query = “SELECT MIN(#date_column), MAX(#date_column) FROM #full_table_name”
283
+ date_result = execute(date_query, format: :array).first
284
+ date_start, date_end = date_result
285
+ end</p>
286
+
287
+ <p>DWH::TableStats.new(
288
+ row_count: row_count,
289
+ date_start: date_start,
290
+ date_end: date_end
291
+ )
292
+ end
293
+ ```</p>
294
+
295
+ <h3 id="implement-query-execution">6. Implement Query Execution</h3>
296
+
297
+ <p>```ruby
298
+ def execute(sql, format: :array, retries: 0)
299
+ response = connection.post(‘/query’, { sql: sql }.to_json)</p>
300
+
301
+ <p>raise DWH::ExecutionError, “Query failed: #responseresponse.body” unless response.success?</p>
302
+
303
+ <p>raw_data = JSON.parse(response.body)
304
+ format_results(raw_data, format)
305
+ rescue =&gt; e
306
+ if retries &gt; 0
307
+ sleep(1)
308
+ execute(sql, format: format, retries: retries - 1)
309
+ else
310
+ raise DWH::ExecutionError, “Query execution failed: #ee.message”
311
+ end
312
+ end</p>
313
+
314
+ <p>def execute_stream(sql, io, stats: nil)
315
+ # For HTTP APIs, you might need to paginate or use streaming endpoints
316
+ offset = 0
317
+ limit = 10_000</p>
318
+
319
+ <p>loop do
320
+ paginated_sql = “#sql LIMIT #limit OFFSET #offset”
321
+ results = execute(paginated_sql, format: :array)</p>
322
+
323
+ <pre class="code ruby"><code class="ruby">break if results.empty?
324
+
325
+ results.each do |row|
326
+ csv_row = CSV.generate_line(row)
327
+ io.write(csv_row)
328
+ stats&amp;.add_row(row)
329
+ end
330
+
331
+ offset += limit end end
332
+ </code></pre>
333
+
334
+ <p>def stream(sql, &amp;block)
335
+ # Similar to execute_stream but yields chunks to block
336
+ offset = 0
337
+ limit = 10_000</p>
338
+
339
+ <p>loop do
340
+ paginated_sql = “#sql LIMIT #limit OFFSET #offset”
341
+ chunk = execute(paginated_sql, format: :array)</p>
342
+
343
+ <pre class="code ruby"><code class="ruby">break if chunk.empty?
344
+
345
+ yield chunk
346
+ offset += limit end end
347
+ </code></pre>
348
+
349
+ <p>private</p>
350
+
351
+ <p>def format_results(raw_data, format)
352
+ case format
353
+ when :array
354
+ raw_data[‘rows’]
355
+ when :object
356
+ columns = raw_data[‘columns’]
357
+ raw_data[‘rows’].map { |row| columns.zip(row).to_h }
358
+ when :csv
359
+ CSV.generate do |csv|
360
+ raw_data[‘rows’].each { |row| csv « row }
361
+ end
362
+ when :native
363
+ raw_data
364
+ else
365
+ raise ArgumentError, “Unsupported format: #format”
366
+ end
367
+ end
368
+ ```</p>
369
+
370
+ <h2 id="creating-custom-settings">Creating Custom Settings</h2>
371
+
372
+ <h3 id="create-settings-file">1. Create Settings File</h3>
373
+
374
+ <p>Create by copying the <a href="https://github.com/stratasite/dwh/blob/main/lib/dwh/settings/base.yml">base settings file</a> to a relative directory like so:<code>settings/mycustom.yml</code></p>
375
+
376
+ <p>```yaml
377
+ # Override base settings for your database</p>
378
+
379
+ <h1 id="function-mappings">Function mappings</h1>
380
+ <p>truncate_date: “DATE_TRUNC(‘@unit’, @exp)”
381
+ date_literal: “DATE(‘@val’)”
382
+ cast: “CAST(@exp AS @type)”</p>
383
+
384
+ <h1 id="string-functions">String functions</h1>
385
+ <p>trim: “LTRIM(RTRIM(@exp))”
386
+ upper_case: “UPPER(@exp)”
387
+ lower_case: “LOWER(@exp)”</p>
388
+
389
+ <h1 id="null-handling">Null handling</h1>
390
+ <p>if_null: “ISNULL(@exp, @when_null)”
391
+ null_if: “CASE WHEN @exp = @target THEN NULL ELSE @exp END”</p>
392
+
393
+ <h1 id="capabilities">Capabilities</h1>
394
+ <p>supports_window_functions: true
395
+ supports_array_functions: false
396
+ supports_common_table_expressions: true
397
+ supports_temp_tables: false</p>
398
+
399
+ <h1 id="query-behavior">Query behavior</h1>
400
+ <p>temp_table_type: “subquery” # options: cte, subquery, temp
401
+ final_pass_measure_join_type: “inner” # inner, left, right, full</p>
402
+
403
+ <h1 id="custom-settings-for-your-database">Custom settings for your database</h1>
404
+ <p>custom_query_prefix: “/* Generated by DWH */”
405
+ max_query_length: 1000000
406
+ ```</p>
407
+
408
+ <h3 id="custom-settings-location">2. Custom Settings Location</h3>
409
+
410
+ <p>```ruby
411
+ class MyCustomAdapter &lt; Adapter
412
+ # Specify custom settings file location
413
+ settings_file_path “/path/to/my_custom_settings.yml”</p>
414
+
415
+ <p># … rest of implementation
416
+ end
417
+ ```</p>
418
+
419
+ <h2 id="advanced-features">Advanced Features</h2>
420
+
421
+ <h3 id="error-handling">Error Handling</h3>
422
+
423
+ <p><code>ruby
424
+ def execute(sql, format: :array, retries: 0)
425
+ # Your execution logic
426
+ rescue MyDatabaseClient::ConnectionError =&gt; e
427
+ raise DWH::ConnectionError, "Database connection failed: #{e.message}"
428
+ rescue MyDatabaseClient::QueryError =&gt; e
429
+ raise DWH::ExecutionError, "Query execution failed: #{e.message}"
430
+ rescue =&gt; e
431
+ raise DWH::AdapterError, "Unexpected error: #{e.message}"
432
+ end
433
+ </code></p>
434
+
435
+ <h3 id="custom-function-translation">Custom Function Translation</h3>
436
+
437
+ <p>```ruby
438
+ def custom_function(expression, param1, param2)
439
+ # Access settings for function templates
440
+ template = settings[:custom_function] || “CUSTOM_FUNC(@exp, @p1, @p2)”</p>
441
+
442
+ <p>template.gsub(‘@exp’, expression)
443
+ .gsub(‘@p1’, param1.to_s)
444
+ .gsub(‘@p2’, param2.to_s)
445
+ end
446
+ ```</p>
447
+
448
+ <h2 id="registration-and-usage">Registration and Usage</h2>
449
+
450
+ <h3 id="register-your-adapter-1">Register Your Adapter</h3>
451
+
452
+ <p>```ruby
453
+ # In your gem or application initialization
454
+ require ‘dwh’
455
+ require ‘my_custom_adapter’</p>
456
+
457
+ <p>DWH.register(:mycustom, DWH::Adapters::MyCustomAdapter)
458
+ ```</p>
459
+
460
+ <h3 id="use-your-adapter">Use Your Adapter</h3>
461
+
462
+ <p>```ruby
463
+ # Create adapter instance
464
+ adapter = DWH.create(:mycustom, {
465
+ host: ‘database.example.com’,
466
+ port: 1234,
467
+ database: ‘analytics’,
468
+ username: ‘analyst’,
469
+ password: ‘secret’
470
+ })</p>
471
+
472
+ <h1 id="use-standard-dwh-interface">Use standard DWH interface</h1>
473
+ <p>tables = adapter.tables
474
+ metadata = adapter.metadata(‘users’)
475
+ results = adapter.execute(“SELECT COUNT(*) FROM users”)
476
+ ```</p>
477
+
478
+ <h2 id="examples-to-study">Examples to Study</h2>
479
+
480
+ <p>Look at existing adapters for implementation patterns:</p>
481
+
482
+ <ul>
483
+ <li><strong>PostgreSQL</strong> (<code>lib/dwh/adapters/postgres.rb</code>) - RDBMS with full SQL support</li>
484
+ <li><strong>Druid</strong> (<code>lib/dwh/adapters/druid.rb</code>) - HTTP API-based adapter</li>
485
+ <li><strong>DuckDB</strong> (<code>lib/dwh/adapters/duck_db.rb</code>) - Embedded database adapter</li>
486
+ </ul>
487
+ </div></div>
488
+
489
+ <div id="footer">
490
+ Generated on Fri Aug 22 08:31:21 2025 by
491
+ <a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
492
+ 0.9.37 (ruby-3.4.4).
493
+ </div>
494
+
495
+ </div>
496
+ </body>
497
+ </html>