ghazel-ar-extensions 0.9.3

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 (45) hide show
  1. data/ChangeLog +145 -0
  2. data/README +169 -0
  3. data/Rakefile +61 -0
  4. data/config/database.yml +7 -0
  5. data/config/database.yml.template +7 -0
  6. data/config/mysql.schema +72 -0
  7. data/config/postgresql.schema +39 -0
  8. data/db/migrate/generic_schema.rb +97 -0
  9. data/db/migrate/mysql_schema.rb +32 -0
  10. data/db/migrate/oracle_schema.rb +5 -0
  11. data/db/migrate/version.rb +4 -0
  12. data/init.rb +31 -0
  13. data/lib/ar-extensions/adapters/abstract_adapter.rb +146 -0
  14. data/lib/ar-extensions/adapters/mysql.rb +10 -0
  15. data/lib/ar-extensions/adapters/oracle.rb +14 -0
  16. data/lib/ar-extensions/adapters/postgresql.rb +9 -0
  17. data/lib/ar-extensions/adapters/sqlite.rb +7 -0
  18. data/lib/ar-extensions/create_and_update/mysql.rb +7 -0
  19. data/lib/ar-extensions/create_and_update.rb +509 -0
  20. data/lib/ar-extensions/csv.rb +309 -0
  21. data/lib/ar-extensions/delete/mysql.rb +3 -0
  22. data/lib/ar-extensions/delete.rb +143 -0
  23. data/lib/ar-extensions/extensions.rb +513 -0
  24. data/lib/ar-extensions/finder_options/mysql.rb +6 -0
  25. data/lib/ar-extensions/finder_options.rb +275 -0
  26. data/lib/ar-extensions/finders.rb +94 -0
  27. data/lib/ar-extensions/foreign_keys.rb +70 -0
  28. data/lib/ar-extensions/fulltext/mysql.rb +44 -0
  29. data/lib/ar-extensions/fulltext.rb +62 -0
  30. data/lib/ar-extensions/import/mysql.rb +50 -0
  31. data/lib/ar-extensions/import/postgresql.rb +0 -0
  32. data/lib/ar-extensions/import/sqlite.rb +22 -0
  33. data/lib/ar-extensions/import.rb +348 -0
  34. data/lib/ar-extensions/insert_select/mysql.rb +7 -0
  35. data/lib/ar-extensions/insert_select.rb +178 -0
  36. data/lib/ar-extensions/synchronize.rb +30 -0
  37. data/lib/ar-extensions/temporary_table/mysql.rb +3 -0
  38. data/lib/ar-extensions/temporary_table.rb +131 -0
  39. data/lib/ar-extensions/union/mysql.rb +6 -0
  40. data/lib/ar-extensions/union.rb +204 -0
  41. data/lib/ar-extensions/util/sql_generation.rb +27 -0
  42. data/lib/ar-extensions/util/support_methods.rb +32 -0
  43. data/lib/ar-extensions/version.rb +9 -0
  44. data/lib/ar-extensions.rb +5 -0
  45. metadata +110 -0
@@ -0,0 +1,131 @@
1
+ module ActiveRecord::Extensions::TemporaryTableSupport # :nodoc:
2
+ def supports_temporary_tables? #:nodoc:
3
+ true
4
+ end
5
+ end
6
+
7
+ class ActiveRecord::Base
8
+ # Returns true if the underlying database connection supports temporary tables
9
+ def self.supports_temporary_tables?
10
+ connection.supports_temporary_tables?
11
+ rescue NoMethodError
12
+ false
13
+ end
14
+
15
+ ######################################################################
16
+ # Creates a temporary table given the passed in options hash. The
17
+ # temporary table is created based off from another table the
18
+ # current model class. This method returns the constant for the new
19
+ # new model. This can also be used with block form (see below).
20
+ #
21
+ # == Parameters
22
+ # * options - the options hash used to define the temporary table.
23
+ #
24
+ # ==== Options
25
+ # <tt>:table_name</tt>::the desired name of the temporary table. If not supplied
26
+ # then a name of "temp_" + the current table_name of the current model
27
+ # will be used.
28
+ # <tt>:like</tt>:: the table model you want to base the temporary tables
29
+ # structure off from. If this is not supplied then the table_name of the
30
+ # current model will be used.
31
+ # <tt>:model_name</tt>:: the name of the model you want to use for the temporary
32
+ # table. This must be compliant with Ruby's naming conventions for
33
+ # constants. If this is not supplied a rails-generated table name will
34
+ # be created which is based off from the table_name of the temporary table.
35
+ # IE: Account.create_temporary_table creates the TempAccount model class
36
+ #
37
+ # ==== Example 1, using defaults
38
+ #
39
+ # class Project < ActiveRecord::Base
40
+ # end
41
+ #
42
+ # > t = Project.create_temporary_table
43
+ # > t.class
44
+ # => "TempProject"
45
+ # > t.superclass
46
+ # => Project
47
+ #
48
+ # This creates a temporary table named 'temp_projects' and creates a constant
49
+ # name TempProject. The table structure is copied from the 'projects' table.
50
+ # TempProject is a subclass of Project as you would expect.
51
+ #
52
+ # ==== Example 2, using <tt>:table_name</tt> and <tt>:model options</tt>
53
+ #
54
+ # Project.create_temporary_table :table_name => 'my_projects', :model => 'MyProject'
55
+ #
56
+ # This creates a temporary table named 'my_projects' and creates a constant named
57
+ # MyProject. The table structure is copied from the 'projects' table.
58
+ #
59
+ # ==== Example 3, using <tt>:like</tt>
60
+ #
61
+ # ActiveRecord::Base.create_temporary_table :like => Project
62
+ #
63
+ # This is the same as calling Project.create_temporary_table.
64
+ #
65
+ # ==== Example 4, using block form
66
+ #
67
+ # Project.create_temporary_table do |t|
68
+ # # ...
69
+ # end
70
+ #
71
+ # Using the block form will automatically drop the temporary table
72
+ # when the block exits. +t+ which is passed into the block is the temporary
73
+ # table class. In the above example +t+ equals TempProject. The block form
74
+ # can be used with all of the available options.
75
+ #
76
+ # === See
77
+ #
78
+ # * +drop+
79
+ #
80
+ ######################################################################
81
+ def self.create_temporary_table(opts={})
82
+ opts[:temporary] ||= !opts[:permanent]
83
+ opts[:like] ||= self
84
+ opts[:table_name] ||= "temp_#{self.table_name}"
85
+ opts[:model_name] ||= ActiveSupport::Inflector.classify(opts[:table_name])
86
+
87
+ if Object.const_defined?(opts[:model_name])
88
+ raise Exception, "Model #{opts[:model_name]} already exists!"
89
+ end
90
+
91
+ like_table_name = opts[:like].table_name || self.table_name
92
+
93
+ connection.execute <<-SQL
94
+ CREATE #{opts[:temporary] ? 'TEMPORARY' : ''} TABLE #{opts[:table_name]}
95
+ LIKE #{like_table_name}
96
+ SQL
97
+
98
+ # Sample evaluation:
99
+ #
100
+ # class ::TempFood < Food
101
+ # set_table_name :temp_food
102
+ #
103
+ # def self.drop
104
+ # connection.execute "DROP TABLE temp_foo"
105
+ # Object.send(:remove_const, self.name.to_sym)
106
+ # end
107
+ # end
108
+ class_eval(<<-RUBY, __FILE__, __LINE__)
109
+ class ::#{opts[:model_name]} < #{self.name}
110
+ set_table_name :#{opts[:table_name]}
111
+
112
+ def self.drop
113
+ connection.execute "DROP TABLE #{opts[:table_name]};"
114
+ Object.send(:remove_const, self.name.to_sym)
115
+ end
116
+ end
117
+ RUBY
118
+
119
+ model = Object.const_get(opts[:model_name])
120
+
121
+ if block_given?
122
+ begin
123
+ yield(model)
124
+ ensure
125
+ model.drop
126
+ end
127
+ else
128
+ return model
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,6 @@
1
+ #insert select functionality is dependent on finder options
2
+ require 'ar-extensions/finder_options/mysql'
3
+
4
+ ActiveRecord::ConnectionAdapters::MysqlAdapter.class_eval do
5
+ include ActiveRecord::Extensions::Union::UnionSupport
6
+ end
@@ -0,0 +1,204 @@
1
+ module ActiveRecord::Extensions::Union#:nodoc:
2
+ module UnionSupport #:nodoc:
3
+ def supports_union? #:nodoc:
4
+ true
5
+ end
6
+ end
7
+ end
8
+
9
+ class ActiveRecord::Base
10
+ supports_extension :union
11
+
12
+ extend ActiveRecord::Extensions::SqlGeneration
13
+ class << self
14
+ # Find a union of two or more queries
15
+ # === Args
16
+ # Each argument is a hash map of options sent to <tt>:find :all</tt>
17
+ # including <tt>:conditions</tt>, <tt>:join</tt>, <tt>:group</tt>,
18
+ # <tt>:having</tt>, and <tt>:limit</tt>
19
+ #
20
+ # In addition the following options are accepted
21
+ # * <tt>:pre_sql</tt> inserts SQL before the SELECT statement of this protion of the +union+
22
+ # * <tt>:post_sql</tt> appends additional SQL to the end of the statement
23
+ # * <tt>:override_select</tt> is used to override the <tt>SELECT</tt> clause of eager loaded associations
24
+ #
25
+ # == Examples
26
+ # Find the union of a San Fran zipcode with a Seattle zipcode
27
+ # union_args1 = {:conditions => ['zip_id = ?', 94010], :select => :phone_number_id}
28
+ # union_args2 = {:conditions => ['zip_id = ?', 98102], :select => :phone_number_id}
29
+ # Contact.find_union(union_args1, union_args2, ...)
30
+ #
31
+ # SQL> (SELECT phone_number_id FROM contacts WHERE zip_id = 94010) UNION
32
+ # (SELECT phone_number_id FROM contacts WHERE zip_id = 98102) UNION ...
33
+ #
34
+ # == Global Options
35
+ # To specify global options that apply to the entire union, specify a hash as the
36
+ # first parameter with a key <tt>:union_options</tt>. Valid options include
37
+ # <tt>:group</tt>, <tt>:having</tt>, <tt>:order</tt>, and <tt>:limit</tt>
38
+ #
39
+ #
40
+ # Example:
41
+ # Contact.find_union(:union_options => {:limit => 10, :order => 'created_on'},
42
+ # union_args1, union_args2, ...)
43
+ #
44
+ # SQL> ((select phone_number_id from contacts ...) UNION (select phone_number_id from contacts ...)) order by created_on limit 10
45
+ #
46
+ def find_union(*args)
47
+ supports_union!
48
+ find_by_sql(find_union_sql(*args))
49
+ end
50
+
51
+ # Count across a union of two or more queries
52
+ # === Args
53
+ # * +column_name+ - The column to count. Defaults to all ('*')
54
+ # * <tt>*args</tt> - Each additional argument is a hash map of options used by <tt>:find :all</tt>
55
+ # including <tt>:conditions</tt>, <tt>:join</tt>, <tt>:group</tt>,
56
+ # <tt>:having</tt>, and <tt>:limit</tt>
57
+ #
58
+ # In addition the following options are accepted
59
+ # * <tt>:pre_sql</tt> inserts SQL before the SELECT statement of this protion of the +union+
60
+ # * <tt>:post_sql</tt> appends additional SQL to the end of the statement
61
+ # * <tt>:override_select</tt> is used to override the <tt>SELECT</tt> clause of eager loaded associations
62
+ #
63
+ # Note that distinct is implied so a record that matches more than one
64
+ # portion of the union is counted only once.
65
+ #
66
+ # == Global Options
67
+ # To specify global options that apply to the entire union, specify a hash as the
68
+ # first parameter with a key <tt>:union_options</tt>. Valid options include
69
+ # <tt>:group</tt>, <tt>:having</tt>, <tt>:order</tt>, and <tt>:limit</tt>
70
+ #
71
+ # == Examples
72
+ # Count the number of people who live in Seattle and San Francisco
73
+ # Contact.count_union(:phone_number_id,
74
+ # {:conditions => ['zip_id = ?, 94010]'},
75
+ # {:conditions => ['zip_id = ?', 98102]})
76
+ # SQL> select count(*) from ((select phone_number_id from contacts ...) UNION (select phone_number_id from contacts ...)) as counter_tbl;
77
+ def count_union(column_name, *args)
78
+ supports_union!
79
+ count_val = calculate_union(:count, column_name, *args)
80
+ (args.length == 1 && args.first[:limit] && args.first[:limit].to_i < count_val) ? args.first[:limit].to_i : count_val
81
+ end
82
+
83
+ protected
84
+
85
+ #do a union of specified calculation. Only for simple calculations
86
+ def calculate_union(operation, column_name, *args)#:nodoc:
87
+ union_options = remove_union_options(args)
88
+
89
+
90
+ if args.length == 1
91
+ column_name = '*' if column_name == :all
92
+ calculate(operation, column_name, args.first.update(union_options))
93
+
94
+ # For more than one map of options, count off the subquery of all the column_name fields unioned together
95
+ # For example, if column_name is phone_number_id the generated query is
96
+ # Contact.calculate_union(:count, :phone_number_id, args)
97
+ # SQL> select count(*) from
98
+ # ((select phone_number_id from contacts ...)
99
+ # UNION
100
+ # (select phone_number_id from contacts ...)) as counter_tbl
101
+ else
102
+ column_name = primary_key if column_name == :all
103
+ column = column_for column_name
104
+ column_name = "#{table_name}.#{column_name}" unless column_name.to_s.include?('.')
105
+
106
+ group_by = union_options.delete(:group)
107
+ having = union_options.delete(:having)
108
+ query_alias = union_options.delete(:query_alias)||"#{operation}_giraffe"
109
+
110
+
111
+ #aggregate_alias should be table_name_id
112
+ aggregate_alias = column_alias_for('', column_name)
113
+ #main alias is operation_table_name_id
114
+ main_aggregate_alias = column_alias_for(operation, column_name)
115
+
116
+ sql = "SELECT "
117
+ sql << (group_by ? "#{group_by}, #{operation}(#{aggregate_alias})" : "#{operation}(*)")
118
+ sql << " AS #{main_aggregate_alias}"
119
+ sql << " FROM ("
120
+
121
+ #by nature of the union the results will always be distinct, so remove distinct column here
122
+ sql << args.inject([]){|l, a|
123
+ calc = "(#{construct_calculation_sql_with_extension('', column_name, a)})"
124
+ #for group by we need to select the group by column also
125
+ calc.gsub!(" AS #{aggregate_alias}", " AS #{aggregate_alias}, #{group_by} ") if group_by
126
+ l << calc
127
+ }.join(" UNION ")
128
+
129
+ add_union_options!(sql, union_options)
130
+
131
+ sql << ") as #{query_alias}"
132
+
133
+ if group_by
134
+ #add groupings
135
+ sql << " GROUP BY #{group_by}"
136
+ sql << " HAVING #{having}" if having
137
+
138
+ calculated_data = connection.select_all(sql)
139
+
140
+ calculated_data.inject(ActiveSupport::OrderedHash.new) do |all, row|
141
+ key = type_cast_calculated_value(row[group_by], column_for(group_by.to_s))
142
+ value = row[main_aggregate_alias]
143
+ all << [key, type_cast_calculated_value(value, column_for(column), operation)]
144
+ end
145
+
146
+ else
147
+ count_by_sql(sql)
148
+ end
149
+ end
150
+ end
151
+
152
+
153
+ #Add Global Union options
154
+ def add_union_options!(sql, options)#:nodoc:
155
+ sql << " GROUP BY #{options[:group]} " if options[:group]
156
+
157
+ if options[:order] || options[:limit]
158
+ scope = scope(:find)
159
+ add_order!(sql, options[:order], scope)
160
+ add_limit!(sql, options, scope)
161
+ end
162
+ sql
163
+ end
164
+
165
+ #Remove the global union options
166
+ def remove_union_options(args)#:nodoc:
167
+ args.first.is_a?(Hash) && args.first.has_key?(:union_options) ? (args.shift)[:union_options] : {}
168
+ end
169
+
170
+ def construct_calculation_sql_with_extension(operation, column_name, options)
171
+ construct_ar_extension_sql(options.merge(:command => '', :keywords => nil, :distinct => nil)) {|sql, o|
172
+ calc_sql = construct_calculation_sql(operation, column_name, options)
173
+
174
+ #this is really gross but prevents us from rewriting construct_calculation_sql
175
+ calc_sql.gsub!(/^SELECT\s/, "SELECT #{options[:keywords]} ") if options[:keywords]
176
+
177
+ sql << calc_sql
178
+ }
179
+ end
180
+
181
+ # Return the sql for union of the query options specified on the command line
182
+ # If the first parameter is a map containing :union_options, use these
183
+ def find_union_sql(*args)#:nodoc:
184
+ options = remove_union_options(args)
185
+
186
+ if args.length == 1
187
+ return finder_sql_to_string(args.first.update(options))
188
+ end
189
+
190
+ sql = args.inject([]) do |sql_list, union_args|
191
+ part = union_args.merge(:force_eager_load => true,
192
+ :override_select => union_args[:select]||"#{quoted_table_name}.*",
193
+ :select => nil)
194
+ sql_list << "(#{finder_sql_to_string(part)})"
195
+ sql_list
196
+ end.join(" UNION ")
197
+
198
+
199
+ add_union_options!(sql, options)
200
+ sql
201
+ end
202
+ end
203
+ end
204
+
@@ -0,0 +1,27 @@
1
+
2
+ #Extend this module on ActiveRecord to access global functions
3
+ module ActiveRecord
4
+ module Extensions
5
+ module SqlGeneration#:nodoc:
6
+
7
+ protected
8
+
9
+ def post_sql_statements(options)#:nodoc:
10
+ connection.post_sql_statements(quoted_table_name, options).join(' ')
11
+ end
12
+
13
+ def pre_sql_statements(options)#:nodoc:
14
+ connection.pre_sql_statements({:command => 'SELECT'}.merge(options)).join(' ').strip + " "
15
+ end
16
+
17
+ def construct_ar_extension_sql(options={}, valid_options = [], &block)#:nodoc:
18
+ options.assert_valid_keys(valid_options)if valid_options.any?
19
+
20
+ sql = pre_sql_statements(options)
21
+ yield sql, options
22
+ sql << post_sql_statements(options)
23
+ sql
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,32 @@
1
+ #Extend this module on ActiveRecord to access global functions
2
+ class ExtensionNotSupported < Exception; end;
3
+
4
+ module ActiveRecord
5
+ module Extensions
6
+ module SupportMethods#:nodoc:
7
+ def supports_extension(name)
8
+ class_eval(<<-EOS, __FILE__, __LINE__)
9
+ def self.supports_#{name}?#:nodoc:
10
+ connection.supports_#{name}?
11
+ rescue NoMethodError
12
+ false
13
+ end
14
+
15
+ def supports_#{name}?#:nodoc:
16
+ self.class.supports_#{name}?
17
+ end
18
+
19
+ def self.supports_#{name}!#:nodoc:
20
+ supports_#{name}? or raise ExtensionNotSupported.new("#{name} extension is not supported. Please require the adapter file.")
21
+ end
22
+
23
+ def supports_#{name}!#:nodoc:
24
+ self.class.supports_#{name}!
25
+ end
26
+ EOS
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ ActiveRecord::Base.send :extend, ActiveRecord::Extensions::SupportMethods
@@ -0,0 +1,9 @@
1
+
2
+ module ActiveRecord # :nodoc:
3
+ module Extensions # :nodoc:
4
+ module VERSION
5
+ MAJOR, MINOR, REVISION = %W( 0 9 2 )
6
+ STRING = [ MAJOR, MINOR, REVISION ].join( '.' )
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ begin ; require 'rubygems' rescue LoadError; end
2
+ require 'active_record' # ActiveRecord loads the Benchmark library automatically
3
+ require 'active_record/version'
4
+
5
+ require File.expand_path(File.join( File.dirname( __FILE__ ), '..', 'init.rb' ))
metadata ADDED
@@ -0,0 +1,110 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ghazel-ar-extensions
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.3
5
+ platform: ruby
6
+ authors:
7
+ - Zach Dennis
8
+ - Mark Van Holstyn
9
+ - Blythe Dunham
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+
14
+ date: 2010-10-28 00:00:00 -07:00
15
+ default_executable:
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ name: activerecord
19
+ type: :runtime
20
+ version_requirement:
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ~>
24
+ - !ruby/object:Gem::Version
25
+ version: "2.1"
26
+ version:
27
+ description: Extends ActiveRecord functionality by adding better finder/query support, as well as supporting mass data import, foreign key, CSV and temporary tables
28
+ email: zach.dennis@gmail.com
29
+ executables: []
30
+
31
+ extensions: []
32
+
33
+ extra_rdoc_files:
34
+ - README
35
+ files:
36
+ - init.rb
37
+ - db/migrate/generic_schema.rb
38
+ - db/migrate/mysql_schema.rb
39
+ - db/migrate/oracle_schema.rb
40
+ - db/migrate/version.rb
41
+ - Rakefile
42
+ - ChangeLog
43
+ - README
44
+ - config/database.yml
45
+ - config/database.yml.template
46
+ - config/mysql.schema
47
+ - config/postgresql.schema
48
+ - lib/ar-extensions/adapters/abstract_adapter.rb
49
+ - lib/ar-extensions/adapters/mysql.rb
50
+ - lib/ar-extensions/adapters/oracle.rb
51
+ - lib/ar-extensions/adapters/postgresql.rb
52
+ - lib/ar-extensions/adapters/sqlite.rb
53
+ - lib/ar-extensions/create_and_update/mysql.rb
54
+ - lib/ar-extensions/create_and_update.rb
55
+ - lib/ar-extensions/csv.rb
56
+ - lib/ar-extensions/delete/mysql.rb
57
+ - lib/ar-extensions/delete.rb
58
+ - lib/ar-extensions/extensions.rb
59
+ - lib/ar-extensions/finders.rb
60
+ - lib/ar-extensions/finder_options/mysql.rb
61
+ - lib/ar-extensions/finder_options.rb
62
+ - lib/ar-extensions/foreign_keys.rb
63
+ - lib/ar-extensions/fulltext/mysql.rb
64
+ - lib/ar-extensions/fulltext.rb
65
+ - lib/ar-extensions/import/mysql.rb
66
+ - lib/ar-extensions/import/postgresql.rb
67
+ - lib/ar-extensions/import/sqlite.rb
68
+ - lib/ar-extensions/import.rb
69
+ - lib/ar-extensions/insert_select/mysql.rb
70
+ - lib/ar-extensions/insert_select.rb
71
+ - lib/ar-extensions/synchronize.rb
72
+ - lib/ar-extensions/temporary_table/mysql.rb
73
+ - lib/ar-extensions/temporary_table.rb
74
+ - lib/ar-extensions/union/mysql.rb
75
+ - lib/ar-extensions/union.rb
76
+ - lib/ar-extensions/util/sql_generation.rb
77
+ - lib/ar-extensions/util/support_methods.rb
78
+ - lib/ar-extensions/version.rb
79
+ - lib/ar-extensions.rb
80
+ has_rdoc: true
81
+ homepage: http://www.continuousthinking.com/tags/arext
82
+ licenses: []
83
+
84
+ post_install_message:
85
+ rdoc_options:
86
+ - --main
87
+ - README
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: "0"
95
+ version:
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: "0"
101
+ version:
102
+ requirements: []
103
+
104
+ rubyforge_project: arext
105
+ rubygems_version: 1.3.5
106
+ signing_key:
107
+ specification_version: 3
108
+ summary: Extends ActiveRecord functionality.
109
+ test_files: []
110
+