sportdb-catalogs 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0e5965c765923646871f9c4b5544ea651c476614
4
- data.tar.gz: 6387ea829d97c82a6aafada09f3caf7fa193a530
2
+ SHA256:
3
+ metadata.gz: 5912ceb7d640f68512000d72620ff285eb8aaba12758fb6eda9db0f2bba6c3c8
4
+ data.tar.gz: 6a1022811416867e7ae9fc7bb4519a4fa6cd42cc6ad12a32b1675a26a3d51ab6
5
5
  SHA512:
6
- metadata.gz: d36f04f85b69b4163c7262b50e690679a8133f409b3712e550e4cb4aaad74c7dbba9760e3d23718ff3e13274fb7d9a9bdaa2e2c1e5c052a43e9d461fb8db94b9
7
- data.tar.gz: aa7666f61f3ed5964e1eb29cba7cf956510d98f63e9ea3c5192a9e7321a79ee5f185e2768208adfe05784b55d2e300a8bd82433ba8142678937652016f60ae42
6
+ metadata.gz: 33efb321b2fa039a836f037acd4c57367bea8f2f5d9faeffa77b5eec047590feaa405eb230c15cf073c7e42a6818f60d73914d4d0bd576b7f5baec1c85686722
7
+ data.tar.gz: 3d2862394fad3aa68e0c970e9b6e74b6ed7f2a34eb04a28e8578ff83ce2379651eb146580593d7b13f75705a7ee55b643e73cce2aee8725391e2fea2e787b684
data/CHANGELOG.md CHANGED
@@ -1,3 +1,4 @@
1
+ ### 1.2.0
1
2
  ### 0.0.1 / 2019-06-29
2
3
 
3
4
  * Everything is new. First release.
data/Manifest.txt CHANGED
@@ -1,17 +1,15 @@
1
1
  CHANGELOG.md
2
2
  Manifest.txt
3
- NOTES.md
4
3
  README.md
5
4
  Rakefile
6
5
  lib/sportdb/catalogs.rb
7
- lib/sportdb/catalogs/catalog.rb
8
- lib/sportdb/catalogs/config.rb
6
+ lib/sportdb/catalogs/base.rb
7
+ lib/sportdb/catalogs/city.rb
8
+ lib/sportdb/catalogs/club.rb
9
+ lib/sportdb/catalogs/country.rb
10
+ lib/sportdb/catalogs/event_info.rb
11
+ lib/sportdb/catalogs/ground.rb
12
+ lib/sportdb/catalogs/league.rb
13
+ lib/sportdb/catalogs/national_team.rb
14
+ lib/sportdb/catalogs/player.rb
9
15
  lib/sportdb/catalogs/version.rb
10
- lib/sportdb/catalogs/wiki_index.rb
11
- test/helper.rb
12
- test/test_clubs.rb
13
- test/test_clubs_history.rb
14
- test/test_countries.rb
15
- test/test_leagues.rb
16
- test/test_national_teams.rb
17
- test/test_wiki_index.rb
data/README.md CHANGED
@@ -5,12 +5,11 @@
5
5
  * bugs :: [github.com/sportdb/sport.db/issues](https://github.com/sportdb/sport.db/issues)
6
6
  * gem :: [rubygems.org/gems/sportdb-catalogs](https://rubygems.org/gems/sportdb-catalogs)
7
7
  * rdoc :: [rubydoc.info/gems/sportdb-catalogs](http://rubydoc.info/gems/sportdb-catalogs)
8
- * forum :: [opensport](http://groups.google.com/group/opensport)
9
8
 
10
9
 
11
10
 
12
11
 
13
- ## Usage
12
+ ## Usage
14
13
 
15
14
  Let's use the [/clubs datasets](https://github.com/openfootball/clubs)
16
15
  (1500+ football clubs from around the world)
@@ -74,55 +73,6 @@ m[0].name; m[0].city; m[0].country
74
73
  # ...
75
74
  ```
76
75
 
77
- Let's print all names that have duplicate (more than one) matching club:
78
-
79
- ``` ruby
80
- CLUBS.mappings.each do |name, clubs|
81
- if clubs.size > 1
82
- puts "#{clubs.size} matching clubs for `#{name}`:"
83
- clubs.each do |club|
84
- puts " - #{club.name}, #{club.city}, #{club.country.name} (#{club.country.key})"
85
- end
86
- puts
87
- end
88
- end
89
- ```
90
-
91
- resulting in:
92
-
93
- ```
94
- 2 matching clubs for `valencia`:
95
- - Valencia FC, Léogâne, Haiti (ht)
96
- - Valencia CF, Valencia, Spain (es)
97
-
98
- 2 matching clubs for `apollon`:
99
- - Apollon Limassol FC, , Cyprus (cy)
100
- - Apollon Smyrnis FC, Athens, Greece (gr)
101
-
102
- 3 matching clubs for `arsenal`:
103
- - Arsenal FC, London, England (eng)
104
- - Arsenal Tula, Tula, Russia (ru)
105
- - Arsenal de Sarandí, Sarandí, Argentina (ar)
106
-
107
- 2 matching clubs for `liverpool`:
108
- - Liverpool FC, Liverpool, England (eng)
109
- - Liverpool Montevideo, Montevideo, Uruguay (uy)
110
-
111
- 2 matching clubs for `barcelona`:
112
- - FC Barcelona, Barcelona, Spain (es)
113
- - Barcelona Guayaquil, Guayaquil, Ecuador (ec)
114
-
115
- 3 matching clubs for `nacional`:
116
- - CD Nacional Madeira, Funchal, Portugal (pt)
117
- - Club Nacional, Asunción, Paraguay (py)
118
- - Nacional de Montevideo, Montevideo, Uruguay (uy)
119
-
120
- 2 matching clubs for `sanjose`:
121
- - San Jose Earthquakes, San Jose, United States (us)
122
- - Club Deportivo San José, Oruro, Bolivia (bo)
123
-
124
- ...
125
- ```
126
76
 
127
77
  That's it.
128
78
 
@@ -136,6 +86,5 @@ Use it as you please with no restrictions whatsoever.
136
86
 
137
87
  ## Questions? Comments?
138
88
 
139
- Send them along to the
140
- [Open Sports & Friends Forum/Mailing List](http://groups.google.com/group/opensport).
141
- Thanks!
89
+ Yes, you can. More than welcome.
90
+ See [Help & Support »](https://github.com/openfootball/help)
data/Rakefile CHANGED
@@ -8,10 +8,10 @@ Hoe.spec 'sportdb-catalogs' do
8
8
  self.summary = "sportdb-catalogs - sport.db (search 'n' find) catalogs for countries, leagues, clubs, national teams, and more"
9
9
  self.description = summary
10
10
 
11
- self.urls = ['https://github.com/sportdb/sport.db']
11
+ self.urls = { home: 'https://github.com/sportdb/sport.db' }
12
12
 
13
13
  self.author = 'Gerald Bauer'
14
- self.email = 'opensport@googlegroups.com'
14
+ self.email = 'gerald.bauer@gmail.com'
15
15
 
16
16
  # switch extension to .markdown for gihub formatting
17
17
  self.readme_file = 'README.md'
@@ -20,11 +20,9 @@ Hoe.spec 'sportdb-catalogs' do
20
20
  self.licenses = ['Public Domain']
21
21
 
22
22
  self.extra_deps = [
23
- ['sportdb-formats', '>= 1.1.3'],
24
- ## dataset libs / gems
25
- ['fifa', '>= 2020.5.18'], ## for (builtin/default) countries
26
- ['footballdb-leagues', '>= 2020.7.7'], ## for (builtin/default) leagues & cups
27
- ['footballdb-clubs', '>= 2020.7.7'], ## for (builtin/default) clubs
23
+ ['sportdb-formats'],
24
+ ['sqlite3'], ## add sqlite for "metal" use (no activerecord etc.)
25
+ ['footballdb-data'] ## add builtin default db
28
26
  ]
29
27
 
30
28
  self.spec_extras = {
@@ -0,0 +1,154 @@
1
+ module CatalogDb
2
+ module Metal
3
+
4
+ ##
5
+ # change BaseRecord to Record again
6
+ ## AND PlayerRecord to PlayerBase or PlayerDatabase ?
7
+ ## AND (Catalog)Record to CatalogBase or CatalogDatabase - why? why not?
8
+
9
+
10
+ class BaseRecord ## or just use Base or such - why? why not?
11
+
12
+ def self.execute( sql )
13
+ ## puts "==> sql query [#{self.name}]"
14
+ ## puts sql
15
+ database.execute( sql )
16
+ end
17
+
18
+ def self.tablename=(name) @tablename = name; end
19
+ def self.tablename() @tablename; end
20
+
21
+ def self.columns=(names)
22
+ ## note: auto-add table name to qualify
23
+ @columns = names.map {|name| "#{self.tablename}.#{name}" }
24
+ @columns
25
+ end
26
+ def self.columns() @columns; end
27
+
28
+ def self.count
29
+ sql = "SELECT count(*) FROM #{self.tablename}"
30
+ rows = execute( sql )
31
+ rows[0][0] # e.g. returns [[241]]
32
+ end
33
+
34
+ ## helpers from country - use a helper module for includes (share with clubs etc.) - why? why not?
35
+ # include NameHelper
36
+ extend SportDb::NameHelper
37
+ ## incl. strip_year( name )
38
+ ## has_year?( name)
39
+ ## strip_lang( name )
40
+ ## normalize( name )
41
+
42
+ def self._to_bool( value )
43
+ if value == 0
44
+ false
45
+ elsif value == 1
46
+ true
47
+ else
48
+ ## use TypeError - why? why not? or if exits ValueError?
49
+ raise ArgumentError, "0 or 1 expected for bool in sqlite; got #{value}"
50
+ end
51
+ end
52
+
53
+
54
+ ##
55
+ # todo/check - make _country available to all dbs/records? why? why not?
56
+
57
+ def self._country( country )
58
+ if country.is_a?( String ) || country.is_a?( Symbol )
59
+ # note: query/find country via catalog db
60
+ rec = Country.find_by_code( country )
61
+ rec = Country.find_by_name( country ) if rec.nil?
62
+ if rec.nil?
63
+ puts "** !!! ERROR !!! - unknown country >#{country}< - no match found, sorry - add to world/countries.txt in config"
64
+ exit 1
65
+ end
66
+ rec
67
+ else
68
+ country ## (re)use country struct - no need to run lookup again
69
+ end
70
+ end
71
+ end # class BaseRecord
72
+
73
+
74
+ class PlayerRecord < BaseRecord
75
+ def self.database
76
+ ### note: only one database for all derived records/tables!!!
77
+ ## thus MUST use @@ and not @!!!!!
78
+ ## todo - change later to built-in database
79
+ ## or download on request???
80
+ @@db ||= SQLite3::Database.new( './players.db' )
81
+ @@db
82
+ end
83
+
84
+ def self.database=(path)
85
+ puts "==> setting (internal) players db to: >#{path}<"
86
+ @@db = SQLite3::Database.new( path )
87
+ pp @@db
88
+ @@db
89
+ end
90
+ end # class PlayerRecord
91
+
92
+
93
+
94
+ ##############
95
+ ### todo (fix) / rename to CatalogRecord - why? why not?
96
+ class Record < BaseRecord
97
+ ## add db alias why? why not?
98
+ def self.database
99
+ ### note: only one database for all derived records/tables!!!
100
+ ## thus MUST use @@ and not @!!!!!
101
+ ##
102
+ ## default to built-in via footballdb-data gem for now!!!
103
+ @@db ||= SQLite3::Database.new( './catalog.db' )
104
+ @@db
105
+ end
106
+
107
+ def self.database=(path)
108
+ puts "==> setting (internal) catalog db to: >#{path}<"
109
+ @@db = SQLite3::Database.new( path )
110
+ pp @@db
111
+ @@db
112
+ end
113
+
114
+ ###########
115
+ ### share common methods for reuse
116
+
117
+ def self._to_league( key )
118
+ League._record( key )
119
+ end
120
+
121
+ def self._to_country( key )
122
+ # note: use cached record or (faster) key lookup on fallback
123
+ Country._record( key )
124
+ end
125
+
126
+ def self._to_city( key ) ### rename; use find_by_key / find_by( key: )
127
+ # note: use cached record or (faster) key lookup on fallback
128
+ City._record( key )
129
+ end
130
+
131
+
132
+ def self._city( city )
133
+ if city.is_a?( String ) || city.is_a?( Symbol )
134
+ # note: query/find country via catalog db
135
+ recs = City.match_by( name: city )
136
+ if recs.empty?
137
+ puts "** !!! ERROR !!! - unknown city >#{city}< - no match found, sorry"
138
+ exit 1
139
+ elsif recs.size > 1
140
+ puts "** !!! ERROR !!! - city >#{city}< - too many matches found (#{recs.size}), sorry"
141
+ pp recs
142
+ exit 1
143
+ end
144
+ recs[0]
145
+ else
146
+ city ## (re)use city struct - no need to run lookup again
147
+ end
148
+ end
149
+ end # class Record
150
+
151
+
152
+
153
+ end # module Metal
154
+ end # module CatalogDb
@@ -0,0 +1,78 @@
1
+ module CatalogDb
2
+ module Metal
3
+
4
+ class City < Record
5
+ self.tablename = 'cities'
6
+
7
+ self.columns = ['key',
8
+ 'name',
9
+ 'alt_names',
10
+ 'country_key', #3
11
+ ]
12
+
13
+ def self.cache() @cache ||= Hash.new; end
14
+
15
+ def self._record( key ) ## use _record! as name - why? why not?
16
+ if (rec = cache[ key ])
17
+ rec ## return cached
18
+ else ## query and cache and return
19
+ rows = execute( <<-SQL )
20
+ SELECT #{self.columns.join(', ')}
21
+ FROM cities
22
+ WHERE cities.key = '#{key}'
23
+ SQL
24
+
25
+ ## todo/fix: also assert for rows == 1 AND NOT MULTIPLE records - why? why not?
26
+ if rows.empty?
27
+ raise ArgumentError, "city record with key #{key} not found"
28
+ else
29
+ _build_city( rows[0] )
30
+ end
31
+ end
32
+ end
33
+
34
+ def self._build_city( row )
35
+ ## note: cache structs by key (do NOT rebuild duplicates; reuse)
36
+ cache[ row[0] ] ||= begin
37
+ city = Sports::City.new(
38
+ key: row[0],
39
+ name: row[1],
40
+ ## fix - add alt_names here too
41
+ country: _to_country( row[3] )
42
+ )
43
+ city
44
+ end
45
+ end
46
+
47
+
48
+ def self._find_by_name( name )
49
+ rows = execute( <<-SQL )
50
+ SELECT #{self.columns.join(', ')}
51
+ FROM cities
52
+ INNER JOIN city_names ON cities.key = city_names.key
53
+ WHERE city_names.name = '#{name}'
54
+ SQL
55
+ rows
56
+ end
57
+
58
+
59
+ ## match - always returns an array (with one or more matches)
60
+ def self.match_by( name: )
61
+ ## note: allow passing in of country key too (auto-counvert)
62
+ ## and country struct too
63
+ ## - country assumes / allows the country key or fifa code for now
64
+
65
+ ## note: ALWAYS unaccent 1st in normalize
66
+ ## - add upstream - why? why not?
67
+ # note: returns empty array (e.g. []) if no match and NOT nil
68
+ nameq = normalize( unaccent(name) )
69
+
70
+ rows = _find_by_name( nameq )
71
+
72
+ rows.map {|row| _build_city( row )}
73
+ end
74
+ end # class City
75
+
76
+ end # module Metal
77
+ end # module CatalogDb
78
+
@@ -0,0 +1,104 @@
1
+ module CatalogDb
2
+ module Metal
3
+
4
+ class Club < Record
5
+ self.tablename = 'clubs'
6
+
7
+ self.columns = ['key',
8
+ 'name',
9
+ 'code',
10
+ 'city', #3
11
+ 'district', #4
12
+ 'country_key', #5
13
+ ]
14
+
15
+ def self._build_club( row )
16
+ ## note: cache structs by key (do NOT rebuild duplicates; reuse)
17
+ @cache ||= Hash.new
18
+ @cache[ row[0] ] ||= begin
19
+ club = Sports::Club.new(
20
+ key: row[0],
21
+ name: row[1],
22
+ code: row[2] )
23
+
24
+ club.city = row[3] ## might be nil
25
+ club.district = row[4]
26
+ ## note: country for now NOT supported
27
+ ## via keyword on init!!!
28
+ ## fix - why? why not?
29
+ club.country = row[5] ? _to_country( row[5] ) : nil
30
+ club
31
+ end
32
+
33
+ end
34
+
35
+
36
+ def self._find_by_name( name )
37
+ rows = execute( <<-SQL )
38
+ SELECT #{self.columns.join(', ')}
39
+ FROM clubs
40
+ INNER JOIN club_names ON clubs.key = club_names.key
41
+ WHERE club_names.name = '#{name}'
42
+ SQL
43
+ rows
44
+ end
45
+
46
+
47
+ def self._find_by_name_and_country( name, country_or_countries )
48
+ sql = <<-SQL
49
+ SELECT #{self.columns.join(', ')}
50
+ FROM clubs
51
+ INNER JOIN club_names ON clubs.key = club_names.key
52
+ WHERE club_names.name = '#{name}' AND
53
+ SQL
54
+
55
+ ## check if single country or n countries
56
+ if country_or_countries.is_a?(Array)
57
+ countries = country_or_countries
58
+ ## add (single) quotes and join with comma
59
+ ## e.g. 'at','de'
60
+ country_list = countries.map {|country| %Q<'#{country}'>}.join(',')
61
+ sql << "clubs.country_key in (#{country_list})\n"
62
+ else ## assume string/symbol (single country key)
63
+ country = country_or_countries
64
+ sql << "clubs.country_key = '#{country}'\n"
65
+ end
66
+
67
+ rows = execute( sql )
68
+ rows
69
+ end
70
+
71
+
72
+
73
+ ## match - always returns an array (with one or more matches)
74
+ def self.match_by( name:,
75
+ country: nil )
76
+ ## note: allow passing in of country key too (auto-counvert)
77
+ ## and country struct too
78
+ ## - country assumes / allows the country key or fifa code for now
79
+
80
+ ## note: ALWAYS unaccent 1st in normalize
81
+ ## - add upstream - why? why not?
82
+ # note: returns empty array (e.g. []) if no match and NOT nil
83
+ nameq = normalize( unaccent(name) )
84
+
85
+ ## todo - check for empty array passed in?
86
+ rows = if country
87
+ ### always wrap country in array
88
+ countries = country.is_a?(Array) ? country : [country]
89
+ country_keys = countries.map {|country| _country( country ).key }
90
+ ## unwrap key if array with single country key
91
+ countryq = country_keys.size == 1 ? country_keys[0] : country_keys
92
+
93
+ _find_by_name_and_country( nameq, countryq )
94
+ else
95
+ _find_by_name( nameq )
96
+ end
97
+
98
+ rows.map {|row| _build_club( row )}
99
+ end
100
+ end # class Club
101
+
102
+ end # module Metal
103
+ end # module CatalogDb
104
+
@@ -0,0 +1,110 @@
1
+
2
+ module CatalogDb
3
+ module Metal
4
+
5
+ class Country < Record
6
+ self.tablename = 'countries'
7
+
8
+ self.columns = ['key',
9
+ 'name',
10
+ 'code',
11
+ 'tags',
12
+ 'alt_names']
13
+
14
+ def self.cache() @cache ||= Hash.new; end
15
+
16
+
17
+ def self._record( key ) ## use _record! as name - why? why not?
18
+ if (rec = cache[ key ])
19
+ rec ## return cached
20
+ else ## query and cache and return
21
+ rows = execute( <<-SQL )
22
+ SELECT #{self.columns.join(', ')}
23
+ FROM countries
24
+ WHERE countries.key = '#{key}'
25
+ SQL
26
+
27
+ ## todo/fix: also assert for rows == 1 AND NOT MULTIPLE records - why? why not?
28
+ if rows.empty?
29
+ raise ArgumentError, "country record with key #{key} not found"
30
+ else
31
+ _build_country( rows[0] )
32
+ end
33
+ end
34
+ end
35
+
36
+
37
+ def self._build_country( row )
38
+ ## note: cache structs by key (do NOT rebuild duplicates; reuse)
39
+ cache[ row[0] ] ||= begin
40
+ ## note: split tags & alt names (PLUS remove leading//trailing spaces)
41
+ tags = row[3].split(',').map {|tag| tag.strip }
42
+ alt_names = row[4].split('|').map {|alt_name| alt_name.strip }
43
+ country = Sports::Country.new(
44
+ key: row[0],
45
+ name: row[1],
46
+ code: row[2],
47
+ tags: tags
48
+ )
49
+ country.alt_names = alt_names
50
+ country
51
+ end
52
+ end
53
+
54
+
55
+ ## fix/todo: add find_by (alias for find_by_name/find_by_code)
56
+ def self.find_by_code( code )
57
+ q = code.to_s.downcase ## allow symbols (and always downcase e.g. AUT to aut etc.)
58
+
59
+ ## note: results in
60
+ ## Côte d'Ivoire => côte d'ivoire
61
+ ## quote will break sql!!!
62
+ ## remove - spaces etc.
63
+ ## for now remove only single quote (will break sql) - add more?
64
+ ##
65
+ ## or use escape_sql_string - possible??
66
+
67
+ q = q.gsub( /[']/, '' )
68
+
69
+
70
+ rows = execute( <<-SQL )
71
+ SELECT #{self.columns.join(', ')}
72
+ FROM countries
73
+ INNER JOIN country_codes ON countries.key = country_codes.key
74
+ WHERE country_codes.code = '#{q}'
75
+ SQL
76
+
77
+ if rows.empty?
78
+ nil
79
+ else
80
+ _build_country( rows[0] )
81
+ end
82
+ end
83
+
84
+
85
+ ##
86
+ ##
87
+ ## note - need to escape name ?
88
+ ## e.g. Côte d'Ivoire
89
+ ## make sure normalize removes single quotes (')!!!
90
+
91
+ def self.find_by_name( name )
92
+ q = normalize( unaccent( name.to_s )) ## allow symbols too (e.g. use to.s first)
93
+
94
+ rows = execute( <<-SQL )
95
+ SELECT #{self.columns.join(', ')}
96
+ FROM countries
97
+ INNER JOIN country_names ON countries.key = country_names.key
98
+ WHERE country_names.name = '#{q}'
99
+ SQL
100
+
101
+ if rows.empty?
102
+ nil
103
+ else
104
+ _build_country( rows[0] )
105
+ end
106
+ end
107
+ end # class Country
108
+ end # module Metal
109
+ end # module CatalogDb
110
+
@@ -0,0 +1,74 @@
1
+ module CatalogDb
2
+ module Metal
3
+
4
+ class EventInfo < Record
5
+ self.tablename = 'event_infos'
6
+
7
+ self.columns = ['league_key',
8
+ 'season',
9
+ 'teams',
10
+ 'matches',
11
+ 'goals',
12
+ 'start_date',
13
+ 'end_date']
14
+
15
+
16
+ def self._build_event_info( row )
17
+ ## note: cache structs by key (do NOT rebuild duplicates; reuse)
18
+ @cache ||= Hash.new
19
+ ## note: move EventInfo from sports/format to structs - why? why not?
20
+ @cache[ row[0] ] ||= SportDb::Import::EventInfo.new(
21
+ league: _to_league( row[0] ),
22
+ season: Season.parse(row[1]),
23
+ teams: row[2],
24
+ matches: row[3],
25
+ goals: row[4],
26
+ start_date: row[5] ? Date.strptime( row[5], '%Y-%m-%d' ) : nil,
27
+ end_date: row[6] ? Date.strptime( row[6], '%Y-%m-%d' ) : nil,
28
+ )
29
+ end
30
+
31
+
32
+ def self.seasons( league )
33
+ league_key = league.is_a?( String ) ? League.find!( league ).key
34
+ : league.key
35
+
36
+ rows = execute( <<-SQL )
37
+ SELECT #{self.columns.join(', ')}
38
+ FROM event_infos
39
+ WHERE event_infos.league_key = '#{league_key}'
40
+ SQL
41
+
42
+ rows.map {|row| _build_event_info( row ) }
43
+ end
44
+
45
+
46
+ def self.find_by( league:, season: )
47
+ league_key = league.is_a?( String ) ? League.find!( league ).key
48
+ : league.key
49
+ season_key = season.is_a?( String ) ? Season.parse(season).key
50
+ : season.key
51
+
52
+ rows = execute( <<-SQL )
53
+ SELECT #{self.columns.join(', ')}
54
+ FROM event_infos
55
+ WHERE event_infos.league_key = '#{league_key}' AND
56
+ event_infos.season = '#{season_key}'
57
+ SQL
58
+
59
+ if rows.empty?
60
+ nil
61
+ elsif rows.size > 1 ## not possible in theory
62
+ puts "** !!! ERROR - expected zero or one rows; got (#{rows.size}):"
63
+ pp rows
64
+ exit 1
65
+ else
66
+ _build_event_info( rows[0] )
67
+ end
68
+ end
69
+
70
+ end # class EventInfo
71
+ end # module Metal
72
+ end # module CatalogDb
73
+
74
+