Skip to content
代码片段 群组 项目
未验证 提交 0ffc1f83 编辑于 作者: Dmitry Gruzd's avatar Dmitry Gruzd 提交者: GitLab
浏览文件

Apply 3 suggestion(s) to 2 file(s)


Co-authored-by: default avatarRavi Kumar <rkumar@gitlab.com>
上级 8d91b131
No related branches found
No related tags found
无相关合并请求
显示
388 个添加8 个删除
...@@ -27,6 +27,9 @@ PATH ...@@ -27,6 +27,9 @@ PATH
remote: gems/gitlab-active-context remote: gems/gitlab-active-context
specs: specs:
gitlab-active-context (0.0.1) gitlab-active-context (0.0.1)
activesupport
connection_pool
pg
zeitwerk zeitwerk
PATH PATH
......
...@@ -27,6 +27,9 @@ PATH ...@@ -27,6 +27,9 @@ PATH
remote: gems/gitlab-active-context remote: gems/gitlab-active-context
specs: specs:
gitlab-active-context (0.0.1) gitlab-active-context (0.0.1)
activesupport
connection_pool
pg
zeitwerk zeitwerk
PATH PATH
......
...@@ -2,6 +2,9 @@ PATH ...@@ -2,6 +2,9 @@ PATH
remote: . remote: .
specs: specs:
gitlab-active-context (0.0.1) gitlab-active-context (0.0.1)
activesupport
connection_pool
pg
zeitwerk zeitwerk
GEM GEM
...@@ -87,6 +90,7 @@ GEM ...@@ -87,6 +90,7 @@ GEM
parser (3.3.6.0) parser (3.3.6.0)
ast (~> 2.4.1) ast (~> 2.4.1)
racc racc
pg (1.5.9)
psych (5.2.1) psych (5.2.1)
date date
stringio stringio
......
...@@ -19,6 +19,9 @@ Gem::Specification.new do |spec| ...@@ -19,6 +19,9 @@ Gem::Specification.new do |spec|
spec.files = Dir['lib/**/*.rb'] spec.files = Dir['lib/**/*.rb']
spec.require_paths = ["lib"] spec.require_paths = ["lib"]
spec.add_dependency 'activesupport'
spec.add_dependency 'connection_pool'
spec.add_dependency 'pg'
spec.add_dependency 'zeitwerk' spec.add_dependency 'zeitwerk'
spec.add_development_dependency 'gitlab-styles' spec.add_development_dependency 'gitlab-styles'
......
# frozen_string_literal: true # frozen_string_literal: true
require "zeitwerk" require 'active_support/core_ext/string/inflections'
require 'active_support/core_ext/module/delegation'
require 'connection_pool'
require 'pg'
require 'zeitwerk'
loader = Zeitwerk::Loader.for_gem loader = Zeitwerk::Loader.for_gem
loader.setup loader.setup
...@@ -8,4 +12,12 @@ module ActiveContext ...@@ -8,4 +12,12 @@ module ActiveContext
def self.configure(...) def self.configure(...)
ActiveContext::Config.configure(...) ActiveContext::Config.configure(...)
end end
def self.config
ActiveContext::Config.current
end
def self.adapter
ActiveContext::Adapter.current
end
end end
# frozen_string_literal: true
module ActiveContext
module Adapter
class << self
def current
@current ||= load_adapter
end
private
def load_adapter
config = ActiveContext::Config.current
return nil unless config.enabled
name, hash = config.databases.first
return nil unless name
adapter = hash.fetch(:adapter)
return nil unless adapter
adapter_klass = adapter.safe_constantize
return nil unless adapter_klass
options = hash.fetch(:options)
adapter_klass.new(options)
end
end
end
end
...@@ -2,27 +2,27 @@ ...@@ -2,27 +2,27 @@
module ActiveContext module ActiveContext
class Config class Config
CONFIG = Struct.new(:enabled, :databases, :logger) Cfg = Struct.new(:enabled, :databases, :logger)
class << self class << self
def configure(&block) def configure(&block)
@instance = new(block) @instance = new(block)
end end
def config def current
@instance&.config || {} @instance&.config || Cfg.new
end end
def enabled? def enabled?
config.enabled || false current.enabled || false
end end
def databases def databases
config.databases || {} current.databases || {}
end end
def logger def logger
config.logger || Logger.new($stdout) current.logger || Logger.new($stdout)
end end
end end
...@@ -31,7 +31,7 @@ def initialize(config_block) ...@@ -31,7 +31,7 @@ def initialize(config_block)
end end
def config def config
struct = CONFIG.new struct = Cfg.new
@config_block.call(struct) @config_block.call(struct)
struct struct
end end
......
# frozen_string_literal: true
module ActiveContext
module Databases
module Concerns
module Adapter
attr_reader :client
delegate :search, to: :client
def initialize(options)
@client = client_klass.new(options)
end
def client_klass
raise NotImplementedError
end
end
end
end
end
# frozen_string_literal: true
module ActiveContext
module Databases
module Concerns
module Client
DEFAULT_PREFIX = 'gitlab'
attr_reader :options
def prefix
options[:prefix] || DEFAULT_PREFIX
end
def search(_)
raise NotImplementedError
end
end
end
end
end
# frozen_string_literal: true
module ActiveContext
module Databases
module Concerns
module QueryResult
include Enumerable
def each
raise NotImplementedError
end
end
end
end
end
# frozen_string_literal: true
module ActiveContext
module Databases
module Postgresql
class Adapter
include ActiveContext::Databases::Concerns::Adapter
def client_klass
ActiveContext::Databases::Postgresql::Client
end
end
end
end
end
# frozen_string_literal: true
module ActiveContext
module Databases
module Postgresql
class Client
include ActiveContext::Databases::Concerns::Client
DEFAULT_POOL_SIZE = 5
DEFAULT_POOL_TIMEOUT = 5
def initialize(options)
@options = options
@pool = ConnectionPool.new(
size: options.fetch(:pool_size, DEFAULT_POOL_SIZE),
timeout: options.fetch(:pool_timeout, DEFAULT_POOL_TIMEOUT)
) do
PG.connect(connection_params)
end
end
def search(_query)
with_connection do |conn|
res = conn.exec('SELECT * FROM pg_stat_activity')
QueryResult.new(res)
end
end
private
def with_connection
@pool.with do |conn|
yield(conn)
end
end
def close
@pool&.shutdown(&:close)
end
def connection_params
{
host: options[:host],
port: options[:port],
dbname: options[:database],
user: options[:username],
password: options[:password],
connect_timeout: options.fetch(:connect_timeout, 5)
}.compact
end
end
end
end
end
# frozen_string_literal: true
module ActiveContext
module Databases
module Postgresql
class QueryResult
include ActiveContext::Databases::Concerns::QueryResult
def initialize(pg_result)
@pg_result = pg_result
end
def each
return enum_for(:each) unless block_given?
pg_result.each do |row|
yield row
end
end
def count
pg_result.ntuples
end
def clear
pg_result.clear if pg_result.respond_to?(:clear)
end
private
attr_reader :pg_result
end
end
end
end
...@@ -28,4 +28,35 @@ ...@@ -28,4 +28,35 @@
expect(ActiveContext::Config.logger).to be_a(::Logger) expect(ActiveContext::Config.logger).to be_a(::Logger)
end end
end end
describe '.config' do
it 'returns the current configuration' do
config = described_class.config
expect(config).to be_a(ActiveContext::Config::Cfg)
end
end
describe '.adapter' do
it 'returns nil when not configured' do
expect(described_class.adapter).to be_nil
end
it 'returns configured adapter' do
described_class.configure do |config|
config.enabled = true
config.databases = {
main: {
adapter: 'ActiveContext::Databases::Postgresql::Adapter',
options: {
host: 'localhost',
port: 5432,
database: 'test_db'
}
}
}
end
expect(described_class.adapter).to be_a(ActiveContext::Databases::Postgresql::Adapter)
end
end
end end
# frozen_string_literal: true
RSpec.describe ActiveContext::Databases::Postgresql::Adapter do
let(:options) do
{
host: 'localhost',
port: 5432,
database: 'test_db',
username: 'user',
password: 'pass'
}
end
subject(:adapter) { described_class.new(options) }
it 'delegates search to client' do
query = ActiveContext::Query.filter(foo: :bar)
expect(adapter.client).to receive(:search).with(query)
adapter.search(query)
end
end
# frozen_string_literal: true
RSpec.describe ActiveContext::Databases::Postgresql::Client do
let(:options) do
{
host: 'localhost',
port: 5432,
database: 'test_db',
username: 'user',
password: 'pass',
pool_size: 2,
pool_timeout: 1
}
end
subject(:client) { described_class.new(options) }
describe '#initialize' do
it 'creates a connection pool' do
expect(ConnectionPool).to receive(:new)
.with(hash_including(size: 2, timeout: 1))
client
end
end
describe '#search' do
let(:connection) { instance_double(PG::Connection) }
let(:query_result) { instance_double(PG::Result) }
before do
allow(PG).to receive(:connect).and_return(connection)
allow(connection).to receive(:exec).and_return(query_result)
end
it 'executes query and returns QueryResult' do
expect(connection).to receive(:exec).with('SELECT * FROM pg_stat_activity')
expect(ActiveContext::Databases::Postgresql::QueryResult)
.to receive(:new).with(query_result)
client.search('test query')
end
end
describe '#prefix' do
it 'returns default prefix when not specified' do
expect(client.prefix).to eq('gitlab')
end
it 'returns configured prefix' do
client = described_class.new(options.merge(prefix: 'custom'))
expect(client.prefix).to eq('custom')
end
end
end
# frozen_string_literal: true
RSpec.describe ActiveContext::Databases::Postgresql::QueryResult do
let(:pg_result) { instance_double(PG::Result) }
subject(:query_result) { described_class.new(pg_result) }
describe '#each' do
it 'yields each row' do
rows = [
{ 'id' => 1, 'name' => 'test1' },
{ 'id' => 2, 'name' => 'test2' }
]
allow(pg_result).to receive(:each).and_yield(rows[0]).and_yield(rows[1])
expect { |b| query_result.each(&b) }.to yield_successive_args(*rows)
end
it 'returns enumerator when no block given' do
expect(query_result.each).to be_a(Enumerator)
end
end
describe '#count' do
it 'returns number of tuples' do
allow(pg_result).to receive(:ntuples).and_return(5)
expect(query_result.count).to eq(5)
end
end
describe '#clear' do
context 'when pg_result responds to clear' do
before do
allow(pg_result).to receive(:respond_to?).with(:clear).and_return(true)
end
it 'clears the result' do
expect(pg_result).to receive(:clear)
query_result.clear
end
end
context 'when pg_result does not respond to clear' do
before do
allow(pg_result).to receive(:respond_to?).with(:clear).and_return(false)
end
it 'does nothing' do
expect(pg_result).not_to receive(:clear)
query_result.clear
end
end
end
end
0% 加载中 .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册