activerecord-pinot-adapter 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 34d05b55c0db37ef45a48eb13874ea21fd328e0d68fbdf1cfda03371401ff123
4
- data.tar.gz: 0732be5d8c9cfc27ab00017d8daee56e07b1e8924996919bd8e673553cc8a635
3
+ metadata.gz: c09ea0dd800fdfd76f57fae011fbe90ef3e42498ea51b58d86cfcd0b22611a89
4
+ data.tar.gz: e336a92e3d15be236bfbfea8cb775be28f9d8b459e8ff04a92fd92071d9915a5
5
5
  SHA512:
6
- metadata.gz: 88cbf5c67cc7dcdf077aaa5ab65b9da04926861acbc588793017f07b06b7cf316fc6984f59e83b36f17ab16543a1521efaa73fa390af85b566ec49641962f2db
7
- data.tar.gz: 104d88ad0b25397026ab01f5a8d6a9f4aa2373adde0de49ba5166b60eeeaccb3605ccfa0b7a1f86ae92a25137c03c58aa85d2cba480a8e3b9790ea8575105e2c
6
+ metadata.gz: 33d9d6d982ec4b36813addc7a4e9463cc1279befb2e1692d7a66aa634e306bb5d7ad7a330c71af8ca3cbb542caf689a02ad4a581b6905f3d4da8a8d36caf7cd5
7
+ data.tar.gz: 63a9ab22309ef70b77a9ef72d319a1311f288c1adb62f72821e2f258bf5465695e228d6e8589bcfcca51479914c415f43c73d451e5f31ccc4510c10591acb115
data/Guardfile ADDED
@@ -0,0 +1,42 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ ## Uncomment and set this to only include directories you want to watch
5
+ # directories %w(app lib config test spec features) \
6
+ # .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
7
+
8
+ ## Note: if you are using the `directories` clause above and you are not
9
+ ## watching the project directory ('.'), then you will want to move
10
+ ## the Guardfile to a watched dir and symlink it back, e.g.
11
+ #
12
+ # $ mkdir config
13
+ # $ mv Guardfile config/
14
+ # $ ln -s config/Guardfile .
15
+ #
16
+ # and, you'll have to watch "config/Guardfile" instead of "Guardfile"
17
+
18
+ guard :minitest do
19
+ # with Minitest::Unit
20
+ watch(%r{^test/(.*)/?test_(.*)\.rb$})
21
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/#{m[1]}test_#{m[2]}.rb" }
22
+ watch(%r{^test/test_helper\.rb$}) { "test" }
23
+
24
+ # with Minitest::Spec
25
+ # watch(%r{^spec/(.*)_spec\.rb$})
26
+ # watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
27
+ # watch(%r{^spec/spec_helper\.rb$}) { 'spec' }
28
+
29
+ # Rails 4
30
+ # watch(%r{^app/(.+)\.rb$}) { |m| "test/#{m[1]}_test.rb" }
31
+ # watch(%r{^app/controllers/application_controller\.rb$}) { 'test/controllers' }
32
+ # watch(%r{^app/controllers/(.+)_controller\.rb$}) { |m| "test/integration/#{m[1]}_test.rb" }
33
+ # watch(%r{^app/views/(.+)_mailer/.+}) { |m| "test/mailers/#{m[1]}_mailer_test.rb" }
34
+ # watch(%r{^lib/(.+)\.rb$}) { |m| "test/lib/#{m[1]}_test.rb" }
35
+ # watch(%r{^test/.+_test\.rb$})
36
+ # watch(%r{^test/test_helper\.rb$}) { 'test' }
37
+
38
+ # Rails < 4
39
+ # watch(%r{^app/controllers/(.*)\.rb$}) { |m| "test/functional/#{m[1]}_test.rb" }
40
+ # watch(%r{^app/helpers/(.*)\.rb$}) { |m| "test/helpers/#{m[1]}_test.rb" }
41
+ # watch(%r{^app/models/(.*)\.rb$}) { |m| "test/unit/#{m[1]}_test.rb" }
42
+ end
@@ -0,0 +1,43 @@
1
+ version: '3.7'
2
+ services:
3
+ pinot-zookeeper:
4
+ image: zookeeper:3.5.6
5
+ container_name: pinot-zookeeper
6
+ ports:
7
+ - "2181:2181"
8
+ environment:
9
+ ZOOKEEPER_CLIENT_PORT: 2181
10
+ ZOOKEEPER_TICK_TIME: 2000
11
+ pinot-controller:
12
+ image: apachepinot/pinot:1.0.0
13
+ command: "StartController -zkAddress pinot-zookeeper:2181"
14
+ container_name: pinot-controller
15
+ restart: unless-stopped
16
+ ports:
17
+ - "9000:9000"
18
+ environment:
19
+ JAVA_OPTS: "-Dplugins.dir=/opt/pinot/plugins -Xms1G -Xmx4G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xloggc:gc-pinot-controller.log"
20
+ depends_on:
21
+ - pinot-zookeeper
22
+ pinot-broker:
23
+ image: apachepinot/pinot:1.0.0
24
+ command: "StartBroker -zkAddress pinot-zookeeper:2181"
25
+ restart: unless-stopped
26
+ container_name: "pinot-broker"
27
+ ports:
28
+ - "8099:8099"
29
+ environment:
30
+ JAVA_OPTS: "-Dplugins.dir=/opt/pinot/plugins -Xms4G -Xmx4G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xloggc:gc-pinot-broker.log"
31
+ depends_on:
32
+ - pinot-controller
33
+ pinot-server:
34
+ image: apachepinot/pinot:1.0.0
35
+ command: "StartServer -zkAddress pinot-zookeeper:2181"
36
+ restart: unless-stopped
37
+ container_name: "pinot-server"
38
+ ports:
39
+ - "8098:8098"
40
+ environment:
41
+ JAVA_OPTS: "-Dplugins.dir=/opt/pinot/plugins -Xms4G -Xmx16G -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -Xloggc:gc-pinot-server.log"
42
+ depends_on:
43
+ - pinot-broker
@@ -0,0 +1,53 @@
1
+ module ActiveRecord
2
+ module ConnectionAdapters
3
+ class PinotAdapter < AbstractAdapter
4
+ module TableStructure
5
+ def self.from_schema(schema)
6
+ fields = []
7
+ schema["dimensionFieldSpecs"].each do |f|
8
+ fields << {
9
+ "name" => f["name"],
10
+ "type" => f["dataType"],
11
+ "pinot_type" => "dimension"
12
+ }
13
+ end
14
+ schema["metricFieldSpecs"].each do |f|
15
+ fields << {
16
+ "name" => f["name"],
17
+ "type" => f["dataType"],
18
+ "pinot_type" => "metric"
19
+ }
20
+ end
21
+ schema["dateTimeFieldSpecs"].each do |f|
22
+ fields << {
23
+ "name" => f.delete("name"),
24
+ "type" => f.delete("dataType"),
25
+ "pinot_type" => "dateTime",
26
+ "metadata" => f
27
+ }
28
+ end
29
+ # normalize values
30
+ fields.each do |f|
31
+ f["type"] = normalize_type(f["type"])
32
+ end
33
+ # set primary keys
34
+ primary_key_columns = schema["primaryKeyColumns"] || []
35
+ primary_key_columns.each do |pk|
36
+ pk_fields = fields.select { |x| x["name"] == pk }
37
+ pk_fields.each { |field| field["pk"] = 1 }
38
+ end
39
+ fields
40
+ end
41
+
42
+ def self.normalize_type(type)
43
+ case type
44
+ when "INT" then "integer"
45
+ when "STRING" then "varchar"
46
+ else
47
+ type
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,129 @@
1
+ require_relative "pinot_adapter/table_structure"
2
+
3
+ module ActiveRecord
4
+ module ConnectionHandling # :nodoc:
5
+ def pinot_adapter_class
6
+ ConnectionAdapters::PinotAdapter
7
+ end
8
+
9
+ def pinot_connection(config)
10
+ pinot_adapter_class.new(config)
11
+ end
12
+ end
13
+
14
+ module ConnectionAdapters
15
+ class PinotAdapter < AbstractAdapter
16
+ TYPES = {
17
+ "INT" => Type::Integer.new,
18
+ "TIMESTAMP" => Type::DateTime.new,
19
+ "FLOAT" => Type::Decimal.new,
20
+ "LONG" => Type::Decimal.new
21
+ }
22
+ def initialize(config = {})
23
+ @pinot_host = config.fetch(:host)
24
+ @pinot_port = config.fetch(:port)
25
+ @pinot_controller_port = config.fetch(:controller_port)
26
+ @pinot_controller_host = config.fetch(:controller_host) || @pinot_host
27
+ # TODO: does it need connection pooling?
28
+ @pinot_client = ::Pinot::Client.new(host: @pinot_host, port: @pinot_port, controller_host: @pinot_controller_host, controller_port: @pinot_controller_port)
29
+
30
+ super(config)
31
+ end
32
+
33
+ def default_prepared_statements
34
+ false
35
+ end
36
+
37
+ def table_structure(table_name)
38
+ schema = @pinot_client.schema(table_name)
39
+ @table_structure = TableStructure.from_schema(schema)
40
+ @table_structure.sort_by! { |x| x[:name] }
41
+ end
42
+
43
+ def new_column_from_field(table_name, field, definitions)
44
+ default = nil
45
+
46
+ type_metadata = fetch_type_metadata(field["type"])
47
+ default_value = extract_value_from_default(default)
48
+ default_function = extract_default_function(default_value, default)
49
+
50
+ Column.new(
51
+ field["name"],
52
+ default_value,
53
+ type_metadata,
54
+ field["notnull"].to_i == 0,
55
+ default_function,
56
+ collation: field["collation"]
57
+ )
58
+ end
59
+ alias_method :column_definitions, :table_structure
60
+
61
+ def extract_value_from_default(default)
62
+ case default
63
+ when /^null$/i
64
+ nil
65
+ # Quoted types
66
+ when /^'([^|]*)'$/m
67
+ $1.gsub("''", "'")
68
+ # Quoted types
69
+ when /^"([^|]*)"$/m
70
+ $1.gsub('""', '"')
71
+ # Numeric types
72
+ when /\A-?\d+(\.\d*)?\z/
73
+ $&
74
+ # Binary columns
75
+ when /x'(.*)'/
76
+ [$1].pack("H*")
77
+ else
78
+ # Anything else is blank or some function
79
+ # and we can't know the value of that, so return nil.
80
+ nil
81
+ end
82
+ end
83
+
84
+ def extract_default_function(default_value, default)
85
+ default if has_default_function?(default_value, default)
86
+ end
87
+
88
+ def has_default_function?(default_value, default)
89
+ !default_value && %r{\w+\(.*\)|CURRENT_TIME|CURRENT_DATE|CURRENT_TIMESTAMP|\|\|}.match?(default)
90
+ end
91
+
92
+ INTEGER_REGEX = /integer/i
93
+ def is_column_the_rowid?(field, column_definitions)
94
+ return false unless INTEGER_REGEX.match?(field["type"]) && field["pk"] == 1
95
+ # is the primary key a single column?
96
+ column_definitions.one? { |c|
97
+ col_pk = c["pk"] || 0
98
+ col_pk > 0
99
+ }
100
+ end
101
+
102
+ def internal_exec_query(sql, name = "SQL", binds = [], prepare: false, async: false) # :nodoc:
103
+ # rows = [
104
+ # [Time.now, 1, 2.0],
105
+ # [Time.now, 2, 2.0],
106
+ # [Time.now, 2, 2.0]
107
+ # ]
108
+ response = @pinot_client.execute(sql)
109
+ rows = response.rows
110
+ columns = response.columns
111
+ columns.transform_values! { |value| TYPES.fetch(value, value) }
112
+ ActiveRecord::Result.new(
113
+ response.columns.keys,
114
+ rows.to_a,
115
+ columns
116
+ )
117
+ end
118
+
119
+ def data_source_sql(name = nil, type: nil)
120
+ end
121
+
122
+ def primary_keys(table_name)
123
+ []
124
+ end
125
+ end
126
+
127
+ ActiveSupport.run_load_hooks(:active_record_pinotadapter, PinotAdapter)
128
+ end
129
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "pinot"
4
+ require "active_record"
5
+ require_relative "../connection_adapters/pinot_adapter"
6
+
7
+ module ActiveRecord
8
+ module Pinot
9
+ module Adapter
10
+ class Error < StandardError; end
11
+ # Your code goes here...
12
+ end
13
+ end
14
+ end
@@ -3,7 +3,7 @@
3
3
  module Activerecord
4
4
  module Pinot
5
5
  module Adapter
6
- VERSION = "0.1.0"
6
+ VERSION = "0.1.1"
7
7
  end
8
8
  end
9
9
  end
@@ -1,12 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "adapter/version"
4
-
5
- module Activerecord
6
- module Pinot
7
- module Adapter
8
- class Error < StandardError; end
9
- # Your code goes here...
10
- end
11
- end
12
- end
4
+ require "pinot"
5
+ require "active_record"
6
+ require "active_record/pinot/adapter"
metadata CHANGED
@@ -1,15 +1,105 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: activerecord-pinot-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Celso Fernandes
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-01-16 00:00:00.000000000 Z
12
- dependencies: []
11
+ date: 2024-05-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 5.2.0
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '7.2'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: 5.2.0
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '7.2'
33
+ - !ruby/object:Gem::Dependency
34
+ name: pinot
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :runtime
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: guard
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: guard-minitest
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ - !ruby/object:Gem::Dependency
76
+ name: minitest-reporters
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0'
89
+ - !ruby/object:Gem::Dependency
90
+ name: minitest-focus
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
13
103
  description: ActiveRecord Apache Pinot Adapter
14
104
  email:
15
105
  - celso.fernandes@clickfunnels.com
@@ -20,9 +110,13 @@ files:
20
110
  - ".standard.yml"
21
111
  - CHANGELOG.md
22
112
  - CODE_OF_CONDUCT.md
113
+ - Guardfile
23
114
  - README.md
24
115
  - Rakefile
25
- - activerecord-pinot-adapter.gemspec
116
+ - docker-compose.yml
117
+ - lib/active_record/connection_adapters/pinot_adapter.rb
118
+ - lib/active_record/connection_adapters/pinot_adapter/table_structure.rb
119
+ - lib/active_record/pinot/adapter.rb
26
120
  - lib/activerecord/pinot/adapter.rb
27
121
  - lib/activerecord/pinot/adapter/version.rb
28
122
  - sig/activerecord/pinot/adapter.rbs
@@ -47,7 +141,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
47
141
  - !ruby/object:Gem::Version
48
142
  version: '0'
49
143
  requirements: []
50
- rubygems_version: 3.4.21
144
+ rubygems_version: 3.5.3
51
145
  signing_key:
52
146
  specification_version: 4
53
147
  summary: ActiveRecord Apache Pinot Adapter
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/activerecord/pinot/adapter/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "activerecord-pinot-adapter"
7
- spec.version = Activerecord::Pinot::Adapter::VERSION
8
- spec.authors = ["Celso Fernandes"]
9
- spec.email = ["celso.fernandes@clickfunnels.com"]
10
-
11
- spec.summary = "ActiveRecord Apache Pinot Adapter"
12
- spec.description = "ActiveRecord Apache Pinot Adapter"
13
- spec.homepage = "https://github.com/fernandes/activerecord-pinot-adapter"
14
- spec.required_ruby_version = ">= 2.6.0"
15
-
16
- spec.metadata["homepage_uri"] = spec.homepage
17
- spec.metadata["source_code_uri"] = "https://github.com/fernandes/activerecord-pinot-adapter"
18
- spec.metadata["changelog_uri"] = "https://github.com/fernandes/activerecord-pinot-adapter/blob/main/CHANGELOG.md"
19
-
20
- # Specify which files should be added to the gem when it is released.
21
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
22
- spec.files = Dir.chdir(__dir__) do
23
- `git ls-files -z`.split("\x0").reject do |f|
24
- (File.expand_path(f) == __FILE__) ||
25
- f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor Gemfile])
26
- end
27
- end
28
- spec.bindir = "exe"
29
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
30
- spec.require_paths = ["lib"]
31
-
32
- # Uncomment to register a new dependency of your gem
33
- # spec.add_dependency "example-gem", "~> 1.0"
34
-
35
- # For more information and examples about making a new gem, check out our
36
- # guide at: https://bundler.io/guides/creating_gem.html
37
- end