arfi 0.5.1 → 1.0.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.
- checksums.yaml +4 -4
- data/.env.sample +2 -0
- data/.rspec +1 -0
- data/.rubocop.yml +8 -1
- data/.rubocop_todo.yml +12 -0
- data/CHANGELOG.md +94 -0
- data/CODE_OF_CONDUCT.md +5 -1
- data/CONTRIBUTING.md +26 -0
- data/README.md +327 -118
- data/SECURITY.md +17 -0
- data/Steepfile +1 -29
- data/compose.yml +33 -0
- data/docscribe.yml +92 -0
- data/gemfiles/rails_6_0.gemfile +11 -0
- data/gemfiles/rails_6_1.gemfile +11 -0
- data/gemfiles/rails_7_0.gemfile +9 -0
- data/gemfiles/rails_7_1.gemfile +10 -0
- data/gemfiles/rails_7_2.gemfile +10 -0
- data/gemfiles/rails_8_0.gemfile +10 -0
- data/gemfiles/rails_8_1.gemfile +10 -0
- data/lib/arfi/cli.rb +24 -9
- data/lib/arfi/commands/f_idx.rb +5 -230
- data/lib/arfi/commands/functions.rb +128 -0
- data/lib/arfi/commands/functions_candidates.rb +133 -0
- data/lib/arfi/commands/functions_creation.rb +157 -0
- data/lib/arfi/commands/functions_helpers.rb +181 -0
- data/lib/arfi/commands/functions_paths.rb +131 -0
- data/lib/arfi/commands/functions_rendering.rb +137 -0
- data/lib/arfi/commands/init.rb +88 -0
- data/lib/arfi/commands/project.rb +5 -51
- data/lib/arfi/errors.rb +15 -3
- data/lib/arfi/extensions/active_record/base.rb +33 -23
- data/lib/arfi/extensions/active_record/connection_adapters/postgresql/database_statements.rb +159 -24
- data/lib/arfi/sql_function_loader.rb +289 -90
- data/lib/arfi/tasks/db.rake +60 -18
- data/lib/arfi/version.rb +1 -1
- data/lib/arfi.rb +2 -0
- data/rbs_collection.lock.yaml +93 -61
- data/sig/compat/active_record_base_compat.rbs +17 -0
- data/sig/compat/thor_dsl.rbs +8 -0
- data/sig/lib/arfi/commands/f_idx.rbs +5 -103
- data/sig/lib/arfi/commands/functions.rbs +99 -0
- data/sig/lib/arfi/commands/functions_candidates.rbs +25 -0
- data/sig/lib/arfi/commands/functions_creation.rbs +29 -0
- data/sig/lib/arfi/commands/functions_helpers.rbs +41 -0
- data/sig/lib/arfi/commands/functions_paths.rbs +25 -0
- data/sig/lib/arfi/commands/functions_rendering.rbs +25 -0
- data/sig/lib/arfi/commands/init.rbs +22 -0
- data/sig/lib/arfi/commands/project.rbs +5 -24
- data/sig/lib/arfi/extensions/active_record/base.rbs +5 -10
- data/sig/lib/arfi/extensions/active_record/connection_adapters/postgresql/database_statements.rbs +28 -0
- data/sig/lib/arfi/sql_function_loader.rbs +45 -87
- data/sig/lib/arfi/tasks/db.rbs +3 -0
- data/sig/lib/arfi/version.rbs +1 -1
- metadata +125 -14
data/docscribe.yml
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
---
|
|
2
|
+
# Docscribe configuration file
|
|
3
|
+
#
|
|
4
|
+
# Docscribe works without this file — create it only for customization.
|
|
5
|
+
#
|
|
6
|
+
# Quick start:
|
|
7
|
+
# bundle exec docscribe lib # check what would change
|
|
8
|
+
# bundle exec docscribe -a lib # apply safe updates
|
|
9
|
+
# bundle exec docscribe -A lib # rebuild all doc blocks
|
|
10
|
+
|
|
11
|
+
emit:
|
|
12
|
+
# What to include in generated documentation
|
|
13
|
+
header: false # +MyClass#foo+ -> ReturnType
|
|
14
|
+
param_tags: true # @param tags
|
|
15
|
+
return_tag: true # @return tag
|
|
16
|
+
visibility_tags: true # @private / @protected
|
|
17
|
+
raise_tags: true # @raise tags
|
|
18
|
+
rescue_conditional_returns: true # @return [Type] if Error
|
|
19
|
+
attributes: false # @!attribute for attr_*
|
|
20
|
+
|
|
21
|
+
# Placeholder text for generated docs
|
|
22
|
+
include_default_message: true # "Method documentation."
|
|
23
|
+
include_param_documentation: true # "Param documentation."
|
|
24
|
+
|
|
25
|
+
doc:
|
|
26
|
+
# Default text and formatting
|
|
27
|
+
default_message: "Method documentation."
|
|
28
|
+
param_documentation: "Param documentation."
|
|
29
|
+
param_tag_style: "type_name" # "type_name" or "name_type"
|
|
30
|
+
sort_tags: true
|
|
31
|
+
tag_order: ["todo", "note", "api", "private", "protected", "param", "option", "yieldparam", "raise", "return"]
|
|
32
|
+
|
|
33
|
+
inference:
|
|
34
|
+
# Type inference behavior
|
|
35
|
+
fallback_type: "Object" # when uncertain
|
|
36
|
+
nil_as_optional: true # String | nil => String?
|
|
37
|
+
treat_options_keyword_as_hash: true # options: keyword => Hash
|
|
38
|
+
|
|
39
|
+
filter:
|
|
40
|
+
# Which methods and files to process
|
|
41
|
+
# Method format: "Container#method" (instance) or "Container.method" (class)
|
|
42
|
+
# Supports globs ("*#initialize") and regex ("/^MyApp::.*$/")
|
|
43
|
+
include: []
|
|
44
|
+
exclude: []
|
|
45
|
+
visibilities: ["public", "protected", "private"]
|
|
46
|
+
scopes: ["instance", "class"]
|
|
47
|
+
|
|
48
|
+
files:
|
|
49
|
+
# File paths relative to project root (globs or /regex/)
|
|
50
|
+
include: []
|
|
51
|
+
exclude: ["spec"]
|
|
52
|
+
|
|
53
|
+
methods:
|
|
54
|
+
# Override defaults per scope and visibility.
|
|
55
|
+
# Empty {} means "use values from `doc` section".
|
|
56
|
+
#
|
|
57
|
+
# Example:
|
|
58
|
+
# instance:
|
|
59
|
+
# public:
|
|
60
|
+
# default_message: "Public API."
|
|
61
|
+
# private:
|
|
62
|
+
# return_tag: false
|
|
63
|
+
instance:
|
|
64
|
+
public: {}
|
|
65
|
+
protected: {}
|
|
66
|
+
private: {}
|
|
67
|
+
class:
|
|
68
|
+
public: {}
|
|
69
|
+
protected: {}
|
|
70
|
+
private: {}
|
|
71
|
+
|
|
72
|
+
rbs:
|
|
73
|
+
# Use RBS signatures for better types (requires `gem "rbs"`)
|
|
74
|
+
enabled: true
|
|
75
|
+
sig_dirs: ["sig"]
|
|
76
|
+
collection_dirs: [] # auto-discovered from --rbs-collection
|
|
77
|
+
collapse_generics: false # Hash<Symbol, String> => Hash
|
|
78
|
+
collection: true # auto-discover from rbs_collection.lock.yaml
|
|
79
|
+
|
|
80
|
+
sorbet:
|
|
81
|
+
# Use Sorbet inline sigs and RBI files for better types
|
|
82
|
+
enabled: false
|
|
83
|
+
rbi_dirs: ["sorbet/rbi", "rbi"]
|
|
84
|
+
collapse_generics: false
|
|
85
|
+
|
|
86
|
+
plugins:
|
|
87
|
+
# Load custom plugins
|
|
88
|
+
# Example:
|
|
89
|
+
# require:
|
|
90
|
+
# - ./docscribe_plugins
|
|
91
|
+
# - docscribe-rails-associations
|
|
92
|
+
require: []
|
data/lib/arfi/cli.rb
CHANGED
|
@@ -1,23 +1,38 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'thor'
|
|
4
|
-
require_relative 'commands/
|
|
5
|
-
require_relative 'commands/
|
|
4
|
+
require_relative 'commands/init'
|
|
5
|
+
require_relative 'commands/functions'
|
|
6
6
|
|
|
7
|
-
# steep:ignore:start
|
|
8
7
|
module Arfi
|
|
9
|
-
# Top
|
|
8
|
+
# Top-level CLI entrypoint for the `arfi` executable.
|
|
9
|
+
#
|
|
10
|
+
# It wires new command groups and keeps backward-compatible aliases:
|
|
11
|
+
# - `arfi init` (preferred)
|
|
12
|
+
# - `arfi functions` (preferred)
|
|
13
|
+
# - `arfi project` (alias for init)
|
|
14
|
+
# - `arfi f_idx` (alias for functions)
|
|
15
|
+
#
|
|
16
|
+
# @api public
|
|
10
17
|
class CLI < Thor
|
|
11
|
-
desc '
|
|
12
|
-
subcommand '
|
|
18
|
+
desc 'init [COMMAND]', 'Initialize ARFI directories (new command). Default: create'
|
|
19
|
+
subcommand 'init', Arfi::Commands::Init
|
|
13
20
|
|
|
14
|
-
desc '
|
|
15
|
-
subcommand '
|
|
21
|
+
desc 'project [COMMAND]', 'Alias for `arfi init` (backward compatible).'
|
|
22
|
+
subcommand 'project', Arfi::Commands::Init
|
|
23
|
+
|
|
24
|
+
desc 'functions [COMMAND]', 'Manage SQL function files (new command). Default: list'
|
|
25
|
+
subcommand 'functions', Arfi::Commands::Functions
|
|
26
|
+
|
|
27
|
+
desc 'f_idx [COMMAND]', 'Alias for `arfi functions` (backward compatible).'
|
|
28
|
+
subcommand 'f_idx', Arfi::Commands::Functions
|
|
16
29
|
|
|
17
30
|
desc 'version', 'Print the version'
|
|
31
|
+
# Print the gem version to stdout.
|
|
32
|
+
#
|
|
33
|
+
# @return [Integer]
|
|
18
34
|
def version
|
|
19
35
|
$stdout.write(Arfi::VERSION, "\n")
|
|
20
36
|
end
|
|
21
37
|
end
|
|
22
38
|
end
|
|
23
|
-
# steep:ignore:end
|
data/lib/arfi/commands/f_idx.rb
CHANGED
|
@@ -1,238 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require 'rails'
|
|
5
|
-
require File.expand_path('config/environment', Dir.pwd)
|
|
3
|
+
require_relative 'functions'
|
|
6
4
|
|
|
7
5
|
module Arfi
|
|
8
6
|
module Commands
|
|
9
7
|
# +Arfi::Commands::FIdx+ module contains commands for manipulating functional index in Rails project.
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
desc 'create FUNCTION_NAME [--template=template_file --adapter=adapter]', 'Initialize the functional index'
|
|
15
|
-
option :template, type: :string, banner: 'template_file',
|
|
16
|
-
desc: 'Path to the template file. See `README.md` for details.'
|
|
17
|
-
option :adapter, type: :string,
|
|
18
|
-
desc: 'Specify database adapter, used for projects with multiple database architecture. ' \
|
|
19
|
-
"Available adapters: #{ADAPTERS.join(', ')}",
|
|
20
|
-
banner: 'adapter'
|
|
21
|
-
# steep:ignore:end
|
|
22
|
-
|
|
23
|
-
# +Arfi::Commands::FIdx#create+ -> void
|
|
24
|
-
#
|
|
25
|
-
# This command is used to create the functional index.
|
|
26
|
-
#
|
|
27
|
-
# @example
|
|
28
|
-
# bundle exec arfi f_idx create some_function
|
|
29
|
-
#
|
|
30
|
-
# ARFI also supports the use of custom templates for SQL functions, but now there are some restrictions and rules
|
|
31
|
-
# according to which it is necessary to describe the function. First, the function must be written in a
|
|
32
|
-
# Ruby-compatible syntax: the file name is not so important, but the name for the function name must be
|
|
33
|
-
# interpolated with the +index_name+ variable name, and the function itself must be placed in the HEREDOC
|
|
34
|
-
# statement. Below is an example file.
|
|
35
|
-
#
|
|
36
|
-
# @example
|
|
37
|
-
# # ./template/my_custom_template
|
|
38
|
-
# <<~SQL
|
|
39
|
-
# CREATE OR REPLACE FUNCTION #{index_name}() RETURNS TEXT[]
|
|
40
|
-
# LANGUAGE SQL
|
|
41
|
-
# IMMUTABLE AS
|
|
42
|
-
# $$
|
|
43
|
-
# -- Function body here
|
|
44
|
-
# $$
|
|
45
|
-
# SQL
|
|
46
|
-
#
|
|
47
|
-
# To use a custom template, add the --template flag.
|
|
48
|
-
#
|
|
49
|
-
# @example
|
|
50
|
-
# bundle exec arfi f_idx create some_function --template ./template/my_custom_template
|
|
51
|
-
#
|
|
52
|
-
# @param index_name [String] Name of the index.
|
|
53
|
-
# @return [void]
|
|
54
|
-
# @raise [Arfi::Errors::InvalidSchemaFormat] if ActiveRecord.schema_format is not :ruby
|
|
55
|
-
# @raise [Arfi::Errors::NoFunctionsDir] if there is no `db/functions` directory
|
|
56
|
-
# @see Arfi::Commands::FIdx#validate_schema_format!
|
|
57
|
-
def create(index_name)
|
|
58
|
-
validate_schema_format!
|
|
59
|
-
content = build_sql_function(index_name)
|
|
60
|
-
create_function_file(index_name, content)
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
# steep:ignore:start
|
|
64
|
-
desc 'destroy INDEX_NAME [--revision=revision --adapter=adapter]', 'Delete the functional index.'
|
|
65
|
-
option :revision, type: :string, banner: 'revision', desc: 'Revision of the function.'
|
|
66
|
-
option :adapter, type: :string,
|
|
67
|
-
desc: 'Specify database adapter, used for projects with multiple database architecture. ' \
|
|
68
|
-
"Available adapters: #{ADAPTERS.join(', ')}",
|
|
69
|
-
banner: 'adapter'
|
|
70
|
-
# steep:ignore:end
|
|
71
|
-
|
|
72
|
-
# +Arfi::Commands::FIdx#destroy+ -> void
|
|
73
|
-
#
|
|
74
|
-
# This command is used to delete the functional index.
|
|
75
|
-
#
|
|
76
|
-
# @example
|
|
77
|
-
# bundle exec arfi f_idx destroy some_function [revision index (just an integer, 1 is by default)]
|
|
78
|
-
# @param index_name [String] Name of the index.
|
|
79
|
-
# @return [void]
|
|
80
|
-
# @raise [Arfi::Errors::InvalidSchemaFormat] if ActiveRecord.schema_format is not :ruby
|
|
81
|
-
def destroy(index_name)
|
|
82
|
-
validate_schema_format!
|
|
83
|
-
|
|
84
|
-
revision = Integer(options[:revision] || '01') # steep:ignore NoMethod
|
|
85
|
-
revision = "0#{revision}"
|
|
86
|
-
FileUtils.rm("#{functions_dir}/#{index_name}_v#{revision}.sql")
|
|
87
|
-
puts "Deleted: #{functions_dir}/#{index_name}_v#{revision}.sql"
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
private
|
|
91
|
-
|
|
92
|
-
# +Arfi::Commands::FIdx#validate_schema_format!+ -> void
|
|
93
|
-
#
|
|
94
|
-
# Helper method to validate the schema format.
|
|
95
|
-
#
|
|
96
|
-
# @!visibility private
|
|
97
|
-
# @private
|
|
98
|
-
# @raise [Arfi::Errors::InvalidSchemaFormat] if ActiveRecord.schema_format is not :ruby.
|
|
99
|
-
# @return [nil] if the schema format is valid.
|
|
100
|
-
def validate_schema_format!
|
|
101
|
-
raise Arfi::Errors::InvalidSchemaFormat unless ActiveRecord.schema_format == :ruby # steep:ignore NoMethod
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
# +Arfi::Commands::FIdx#build_sql_function+ -> String
|
|
105
|
-
#
|
|
106
|
-
# Helper method to build the SQL function.
|
|
107
|
-
#
|
|
108
|
-
# @!visibility private
|
|
109
|
-
# @private
|
|
110
|
-
# @param index_name [String] Name of the index.
|
|
111
|
-
# @return [String] SQL function body.
|
|
112
|
-
def build_sql_function(index_name) # rubocop:disable Metrics/MethodLength
|
|
113
|
-
return build_from_file(index_name) if options[:template] # steep:ignore NoMethod
|
|
114
|
-
|
|
115
|
-
unless options[:adapter] # steep:ignore NoMethod
|
|
116
|
-
return <<~SQL
|
|
117
|
-
CREATE OR REPLACE FUNCTION #{index_name}() RETURNS TEXT[]
|
|
118
|
-
LANGUAGE SQL
|
|
119
|
-
IMMUTABLE AS
|
|
120
|
-
$$
|
|
121
|
-
-- Function body here
|
|
122
|
-
$$
|
|
123
|
-
SQL
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
case options[:adapter] # steep:ignore NoMethod
|
|
127
|
-
when 'postgresql'
|
|
128
|
-
<<~SQL
|
|
129
|
-
CREATE OR REPLACE FUNCTION #{index_name}() RETURNS TEXT[]
|
|
130
|
-
LANGUAGE SQL
|
|
131
|
-
IMMUTABLE AS
|
|
132
|
-
$$
|
|
133
|
-
-- Function body here
|
|
134
|
-
$$
|
|
135
|
-
SQL
|
|
136
|
-
when 'mysql'
|
|
137
|
-
<<~SQL
|
|
138
|
-
CREATE FUNCTION #{index_name} ()
|
|
139
|
-
RETURNS return_type
|
|
140
|
-
BEGIN
|
|
141
|
-
-- Function body here
|
|
142
|
-
END;
|
|
143
|
-
SQL
|
|
144
|
-
else
|
|
145
|
-
# steep:ignore:start
|
|
146
|
-
raise "Unknown adapter: #{options[:adapter]}. Supported adapters: #{ADAPTERS.join(', ')}"
|
|
147
|
-
# steep:ignore:end
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
# +Arfi::Commands::FIdx#build_from_file+ -> String
|
|
152
|
-
#
|
|
153
|
-
# Helper method to build the SQL function. Used with flag `--template`.
|
|
154
|
-
#
|
|
155
|
-
# @!visibility private
|
|
156
|
-
# @private
|
|
157
|
-
# @param index_name [String] Name of the index.
|
|
158
|
-
# @return [String] SQL function body.
|
|
159
|
-
# @see Arfi::Commands::FIdx#create
|
|
160
|
-
# @see Arfi::Commands::FIdx#build_sql_function
|
|
161
|
-
def build_from_file(index_name)
|
|
162
|
-
# steep:ignore:start
|
|
163
|
-
RubyVM::InstructionSequence.compile("index_name = '#{index_name}'; #{File.read(options[:template])}").eval
|
|
164
|
-
# steep:ignore:end
|
|
165
|
-
end
|
|
166
|
-
|
|
167
|
-
# +Arfi::Commands::FIdx#create_function_file+ -> void
|
|
168
|
-
#
|
|
169
|
-
# Helper method to create the index file.
|
|
170
|
-
#
|
|
171
|
-
# @!visibility private
|
|
172
|
-
# @private
|
|
173
|
-
# @param index_name [String] Name of the index.
|
|
174
|
-
# @param content [String] SQL function body.
|
|
175
|
-
# @return [void]
|
|
176
|
-
def create_function_file(index_name, content)
|
|
177
|
-
existing_files = Dir.glob("#{functions_dir}/#{index_name}*.sql")
|
|
178
|
-
|
|
179
|
-
return write_file(index_name, content, 1) if existing_files.empty?
|
|
180
|
-
|
|
181
|
-
latest_version = extract_latest_version(existing_files)
|
|
182
|
-
write_file(index_name, content, latest_version.succ)
|
|
183
|
-
end
|
|
184
|
-
|
|
185
|
-
# +Arfi::Commands::FIdx#extract_latest_version+ -> Integer
|
|
186
|
-
#
|
|
187
|
-
# Helper method to extract the latest version of the index.
|
|
188
|
-
#
|
|
189
|
-
# @!visibility private
|
|
190
|
-
# @private
|
|
191
|
-
# @param files [Array<String>] List of files.
|
|
192
|
-
# @return [String] Latest version of the index.
|
|
193
|
-
def extract_latest_version(files)
|
|
194
|
-
version_numbers = files.map do |file|
|
|
195
|
-
File.basename(file)[/\w+_v(\d+)\.sql/, 1]
|
|
196
|
-
end.compact
|
|
197
|
-
|
|
198
|
-
version_numbers.max
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
# +Arfi::Commands::FIdx#write_file+ -> void
|
|
202
|
-
#
|
|
203
|
-
# Helper method to write the index file.
|
|
204
|
-
#
|
|
205
|
-
# @!visibility private
|
|
206
|
-
# @private
|
|
207
|
-
# @param index_name [String] Name of the index.
|
|
208
|
-
# @param content [String] SQL function body.
|
|
209
|
-
# @param version [String|Integer] Version of the index.
|
|
210
|
-
# @return [void]
|
|
211
|
-
def write_file(index_name, content, version)
|
|
212
|
-
version_str = format('%02d', version)
|
|
213
|
-
path = "#{functions_dir}/#{index_name}_v#{version_str}.sql"
|
|
214
|
-
File.write(path, content.to_s)
|
|
215
|
-
puts "Created: #{path}"
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
# +Arfi::Commands::FIdx#functions_dir+ -> Pathname
|
|
219
|
-
#
|
|
220
|
-
# Helper method to get path to `db/functions` directory.
|
|
221
|
-
#
|
|
222
|
-
# @!visibility private
|
|
223
|
-
# @private
|
|
224
|
-
# @return [Pathname] Path to `db/functions` directory
|
|
225
|
-
def functions_dir
|
|
226
|
-
# steep:ignore:start
|
|
227
|
-
if options[:adapter]
|
|
228
|
-
raise Arfi::Errors::AdapterNotSupported unless ADAPTERS.include?(options[:adapter].to_sym)
|
|
229
|
-
|
|
230
|
-
Rails.root.join("db/functions/#{options[:adapter]}")
|
|
231
|
-
# steep:ignore:end
|
|
232
|
-
else
|
|
233
|
-
Rails.root.join('db/functions')
|
|
234
|
-
end
|
|
235
|
-
end
|
|
236
|
-
end
|
|
8
|
+
#
|
|
9
|
+
# Backward-compatible constant for code that references Arfi::Commands::FIdx.
|
|
10
|
+
# @deprecated
|
|
11
|
+
FIdx = Functions
|
|
237
12
|
end
|
|
238
13
|
end
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'thor'
|
|
4
|
+
require 'rails'
|
|
5
|
+
require 'fileutils'
|
|
6
|
+
require 'json'
|
|
7
|
+
|
|
8
|
+
require_relative 'functions_helpers'
|
|
9
|
+
require_relative 'functions_creation'
|
|
10
|
+
require_relative 'functions_paths'
|
|
11
|
+
require_relative 'functions_rendering'
|
|
12
|
+
require_relative 'functions_candidates'
|
|
13
|
+
|
|
14
|
+
module Arfi
|
|
15
|
+
module Commands
|
|
16
|
+
ADAPTERS = %i[postgresql mysql trilogy].freeze
|
|
17
|
+
ROOT_DIR = 'db/functions'
|
|
18
|
+
DEFAULT_SCHEMA = 'public'
|
|
19
|
+
IDENT = /\A[a-zA-Z_][a-zA-Z0-9_]*\z/.freeze
|
|
20
|
+
|
|
21
|
+
# Thor CLI for managing SQL function files.
|
|
22
|
+
class Functions < Thor
|
|
23
|
+
include FunctionsHelpers
|
|
24
|
+
include FunctionsCreation
|
|
25
|
+
include FunctionsPaths
|
|
26
|
+
include FunctionsRendering
|
|
27
|
+
include FunctionsCandidates
|
|
28
|
+
|
|
29
|
+
default_task :list
|
|
30
|
+
|
|
31
|
+
# UX aliases
|
|
32
|
+
map %w[ls] => :list
|
|
33
|
+
map %w[rm delete del] => :destroy
|
|
34
|
+
map %w[new add] => :create
|
|
35
|
+
|
|
36
|
+
# steep:ignore:start
|
|
37
|
+
desc(
|
|
38
|
+
'create FUNCTION_NAME [--schema=schema --template=template_file --adapter=adapter --force]',
|
|
39
|
+
"Create (or overwrite with --force) a SQL function file.\n " \
|
|
40
|
+
"Generic public: db/functions/public/<function>.sql\n " \
|
|
41
|
+
"PostgreSQL public: db/functions/postgresql/public/<function>.sql\n " \
|
|
42
|
+
"PostgreSQL schema: db/functions/postgresql/<schema>/<function>.sql\n" \
|
|
43
|
+
"Schema can be passed as 'schema.function' or via --schema (PostgreSQL only)."
|
|
44
|
+
)
|
|
45
|
+
option :schema, type: :string, banner: 'schema',
|
|
46
|
+
desc: "PostgreSQL schema name (alternative to 'schema.function')."
|
|
47
|
+
option :template, type: :string, banner: 'template_file',
|
|
48
|
+
desc: 'Path to the template file. See README.md for details.'
|
|
49
|
+
option :adapter, type: :string,
|
|
50
|
+
desc: "Specify database adapter. Available adapters: #{ADAPTERS.join(', ')}",
|
|
51
|
+
banner: 'adapter'
|
|
52
|
+
option :force, type: :boolean, default: false,
|
|
53
|
+
desc: 'Overwrite existing function file if it already exists.'
|
|
54
|
+
# steep:ignore:end
|
|
55
|
+
# Create (or overwrite with --force) a SQL function file in the appropriate directory.
|
|
56
|
+
#
|
|
57
|
+
# @param [String] function_ref Function reference string (e.g. 'my_func' or 'schema.my_func')
|
|
58
|
+
# @return [void]
|
|
59
|
+
def create(function_ref)
|
|
60
|
+
validate_schema_format!
|
|
61
|
+
validate_adapter_option!
|
|
62
|
+
|
|
63
|
+
schema, function_name = parse_function_ref(function_ref)
|
|
64
|
+
validate_identifiers!(schema, function_name)
|
|
65
|
+
|
|
66
|
+
ensure_dirs!(adapter: adapter_opt, schema: schema)
|
|
67
|
+
|
|
68
|
+
content = build_sql_function(schema, function_name, original_ref: function_ref)
|
|
69
|
+
write_file(schema, function_name, content)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# steep:ignore:start
|
|
73
|
+
desc(
|
|
74
|
+
'destroy FUNCTION_NAME [--schema=schema --adapter=adapter]',
|
|
75
|
+
'Delete a SQL function file (supports both new and legacy locations).'
|
|
76
|
+
)
|
|
77
|
+
option :schema, type: :string, banner: 'schema',
|
|
78
|
+
desc: "PostgreSQL schema name (alternative to 'schema.function')."
|
|
79
|
+
option :adapter, type: :string,
|
|
80
|
+
desc: "Specify database adapter. Available adapters: #{ADAPTERS.join(', ')}",
|
|
81
|
+
banner: 'adapter'
|
|
82
|
+
# steep:ignore:end
|
|
83
|
+
# Delete a SQL function file from disk (supports both new and legacy locations).
|
|
84
|
+
#
|
|
85
|
+
# @param [String] function_ref Function reference string (e.g. 'my_func' or 'schema.my_func')
|
|
86
|
+
# @return [void]
|
|
87
|
+
def destroy(function_ref)
|
|
88
|
+
validate_schema_format!
|
|
89
|
+
validate_adapter_option!
|
|
90
|
+
|
|
91
|
+
schema, function_name = parse_function_ref(function_ref)
|
|
92
|
+
validate_identifiers!(schema, function_name)
|
|
93
|
+
|
|
94
|
+
ensure_dirs!(adapter: adapter_opt, schema: schema)
|
|
95
|
+
|
|
96
|
+
remove_function_file(schema, function_name)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# steep:ignore:start
|
|
100
|
+
desc(
|
|
101
|
+
'list [--adapter=adapter] [--format=table|paths|json] [--all]',
|
|
102
|
+
"List SQL function files ARFI would load for the chosen adapter.\n" \
|
|
103
|
+
"Default: inferred adapter from Rails config. Default output: table.\n" \
|
|
104
|
+
'--all shows shadowed/overridden candidates too.'
|
|
105
|
+
)
|
|
106
|
+
option :adapter, type: :string,
|
|
107
|
+
desc: "Specify database adapter. Available adapters: #{ADAPTERS.join(', ')}",
|
|
108
|
+
banner: 'adapter'
|
|
109
|
+
option :format, type: :string, default: 'table',
|
|
110
|
+
desc: 'Output format: table, paths, json'
|
|
111
|
+
option :all, type: :boolean, default: false,
|
|
112
|
+
desc: 'Show all candidates (including overridden ones), not just the effective set.'
|
|
113
|
+
# steep:ignore:end
|
|
114
|
+
# List SQL function files ARFI would load for the chosen adapter.
|
|
115
|
+
#
|
|
116
|
+
# @return [void]
|
|
117
|
+
def list
|
|
118
|
+
validate_schema_format!
|
|
119
|
+
validate_adapter_option!
|
|
120
|
+
|
|
121
|
+
adapter = resolve_adapter
|
|
122
|
+
rows = resolve_functions_for(adapter: adapter)
|
|
123
|
+
|
|
124
|
+
render_list(rows)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|