Module: DWH::Factory

Includes:
Logger
Included in:
DWH
Defined in:
lib/dwh/factory.rb

Overview

Manages adapters. This should be the means by which adapters are created, loaded, and pooled.

Instance Method Summary collapse

Methods included from Logger

#logger, logger

Instance Method Details

#adapter?(adapter_name) ⇒ Boolean

Check if the given adapter is registered

Parameters:

  • adapter_name (String, Symbol)

Returns:

  • (Boolean)


35
36
37
# File 'lib/dwh/factory.rb', line 35

def adapter?(adapter_name)
  adapters.key?(adapter_name.to_sym)
end

#adaptersObject

Get the list of registed adapters



40
41
42
# File 'lib/dwh/factory.rb', line 40

def adapters
  @adapters ||= {}
end

#create(adapter_name, config) ⇒ Object

The canonical way of creating an adapter instance in DWH.

Examples:

connect to MySQL

DWH.create(:mysql, { host: '127.0.0.1', databse: 'mydb', username: 'me', password: 'mypwd', client_name: 'Strata CLI'})

connect Trino

DWH.create(:trino, {host: 'localhost', catalog: 'native', username: 'Ajo'})

connect to Druid

DWH.create(:druid, {host: 'localhost',port: 8080, protocol: 'http'})

Parameters:

  • adapter_name (String, Symbol)
  • config (Hash)

    options hash for the target database



65
66
67
# File 'lib/dwh/factory.rb', line 65

def create(adapter_name, config)
  get_adapter(adapter_name).new(config)
end

#get_adapter(adapter_name) ⇒ Object

Get the adapter.

Parameters:

  • adapter_name (String, Symbol)


27
28
29
30
31
# File 'lib/dwh/factory.rb', line 27

def get_adapter(adapter_name)
  raise "Adapter '#{adapter_name}' not found. Did you forget to register it: DWH.register(MyAdapterClass)" unless adapter?(adapter_name)

  adapters[adapter_name.to_sym]
end

#pool(name, adapter_name, config, timeout: 5, size: 10) ⇒ Object

Create a pool of connections for a given name and adapter. Returns existing pool if it was already created.

Parameters:

  • name (String, Symbol)

    custom name for your pool (stored as String)

  • adapter_name (String, Symbol)
  • config (Hash)

    connection options

  • timeout (Integer) (defaults to: 5)

    pool checkout time out

  • size (Integer) (defaults to: 10)

    size of the pool



77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/dwh/factory.rb', line 77

def pool(name, adapter_name, config, timeout: 5, size: 10)
  name = name.to_s
  pool_mutex.synchronize do
    if pools.key?(name)
      pools[name]
    else
      pools[name] = ConnectionPool.new(size: size, timeout: timeout) do
        create(adapter_name, config)
      end
    end
  end
end

#pool_mutexObject

Mutex guarding pool creation / map mutation.



50
51
52
# File 'lib/dwh/factory.rb', line 50

def pool_mutex
  @pool_mutex ||= Mutex.new
end

#poolsObject

Current active pools



45
46
47
# File 'lib/dwh/factory.rb', line 45

def pools
  @pools ||= {}
end

#reaper_tick(frequency = 300) ⇒ Object

One reaper cycle: log pool stats (without checking out) and reap idle connections. Safe to call from tests; rescues per-pool so a shut-down pool cannot kill the loop.

Parameters:

  • frequency (Integer) (defaults to: 300)

    idle threshold passed to ConnectionPool#reap



133
134
135
136
137
138
139
140
141
142
143
144
145
146
# File 'lib/dwh/factory.rb', line 133

def reaper_tick(frequency = 300)
  # Snapshot so concurrent shutdown deletions do not mutate while we iterate.
  pools.to_a.each do |name, pool|
    logger.info "DB POOL FOR #{name} STATS:"
    logger.info "\tSize:      #{pool.size}"
    logger.info "\tIdle:      #{pool.idle}"
    logger.info "\tAvailable: #{pool.available}"
    pool.reap(frequency) { it.close }
  rescue ConnectionPool::PoolShuttingDownError => e
    logger.info "Skipping reaper for pool #{name}: #{e.class}"
  rescue StandardError => e
    logger.error "Reaper error for pool #{name}: #{e.class}: #{e.message}"
  end
end

#register(adapter_name, adapter_class) ⇒ Object

Register your new adapter.

Parameters:

  • adapter_name (String, Symbol)

    your adapter name. Could be different from the class name.

  • adapter_class (Class)

    actual class of the adapter.

Raises:



13
14
15
16
17
18
# File 'lib/dwh/factory.rb', line 13

def register(adapter_name, adapter_class)
  raise ConfigError, 'adapter_class should be a class' unless adapter_class.is_a?(Class)

  adapter_class.load_settings
  adapters[adapter_name.to_sym] = adapter_class
end

#shutdown(pool = nil) ⇒ Object

Shutdown a specific pool or all pools

Parameters:

  • pool (String, Symbol, ConnectionPool, nil) (defaults to: nil)

    pool or name of pool or nil to shut everything down



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/dwh/factory.rb', line 93

def shutdown(pool = nil)
  # Mutate the map under the same mutex as pool creation so a concurrent
  # create cannot be orphaned by @pools = {} / delete racing it.
  # Close outside the lock — ConnectionPool#shutdown can wait on check-in.
  to_close = pool_mutex.synchronize do
    case pool
    when String, Symbol
      # Delete first so a raising close cannot leave a dead pool in the map.
      removed = pools.delete(pool.to_s)
      removed ? [removed] : []
    when ConnectionPool
      key = pools.key(pool)
      pools.delete(key) if key
      [pool]
    else
      closing = pools.values
      @pools = {}
      closing
    end
  end
  to_close.each { |p| p.shutdown { it.close } }
end

#start_reaper(frequency = 300) ⇒ Thread

Start reaper that will periodically clean up unused or idle connections.

Parameters:

  • frequency (Integer) (defaults to: 300)

    defaults to 300 seconds

Returns:

  • (Thread)

    the reaper thread



120
121
122
123
124
125
126
127
128
# File 'lib/dwh/factory.rb', line 120

def start_reaper(frequency = 300)
  logger.info 'Starting DB Adapter reaper process'
  Thread.new do
    loop do
      reaper_tick(frequency)
      sleep frequency
    end
  end
end

#unregister(adapter_name) ⇒ Object

Remove the given adapter from the registry.



21
22
23
# File 'lib/dwh/factory.rb', line 21

def unregister(adapter_name)
  adapters.delete adapter_name.to_sym
end