Model.query_to_hash()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 3
rs 10
1
require 'midb/server_controller'
2
require 'midb/dbengine_model'
3
require 'midb/server_view'
4
require 'midb/hooks'
5
6
require 'sqlite3'
7
require 'json'
8
require 'cgi'
9
module MIDB
10
  module API
11
    class Model
12
13
      attr_accessor :jsf, :db, :engine, :hooks
14
15
      # Constructor
16
      #
17
      # @param jsf [String] JSON file with the schema
18
      # @param db [String] Database to operate on.
19
      # @param engine [Object] Reference to the API engine.
20
      #
21
      # @notice that @hooks (the hooks) are taken from the engine.
22
      def initialize(jsf, db, engine)
23
        @jsf = jsf
24
        @db = db
25
        @engine = engine
26
        @hooks = engine.hooks
27
      end
28
29
      # Safely get the structure
30
      def get_structure()
31
        JSON.parse(IO.read("./json/#{@jsf}.json"))["id"]
32
      end
33
34
      # Convert a HTTP query string to a JSONable hash
35
      #
36
      # @param query [String] HTTP query string
37
      def query_to_hash(query)
38
        Hash[CGI.parse(query).map {|key,values| [key, values[0]||true]}]
39
      end
40
41
      # Act on POST requests - create a new resource
42
      #
43
      # @param data [String] The HTTP query string containing what to POST.
44
      def post(data)
45
        jss = self.get_structure() # For referencing purposes
46
47
        input = self.query_to_hash(data)
48
        bad_request = false
49
        resp = nil
50
        jss.each do |key, value|
51
          # Check if we have it on the query too
52
          unless input.has_key? key
53
            resp = MIDB::Interface::Server.json_error(400, "Bad Request - Not enough data for a new resource")
54
            @engine.http_status = 400
55
            bad_request = true
56
          end
57
        end
58
        input.each do |key, value|
59
          # Check if we have it on the structure too
60
          unless jss.has_key? key
61
            resp = MIDB::Interface::Server.json_error(400, "Bad Request - Wrong argument #{key}")
62
            @engine.http_status = 400
63
            bad_request = true
64
          end
65
        end
66
        
67
68
        # Insert the values if we have a good request
69
        unless bad_request
70
          fields = Hash.new
71
          inserts = Hash.new
72
          main_table = self.get_structure.values[0].split('/')[0]
73
          input.each do |key, value|
74
            struct = jss[key]
75
            table = struct.split("/")[0]
76
            inserts[table] ||= []
77
            fields[table] ||= []
78
            inserts[table].push "\"" + value + "\""
79
            fields[table].push struct.split("/")[1]
80
            if struct.split("/").length > 2
81
              match = struct.split("/")[2]
82
              matching_field = match.split("->")[0]
83
              row_field = match.split("->")[1]
84
              fields[table].push matching_field
85
              if @engine.config["dbengine"] == :mysql
86
                inserts[table].push "(SELECT #{row_field} FROM #{main_table} WHERE id=(SELECT LAST_INSERT_ID()))"
87
              else
88
                inserts[table].push "(SELECT #{row_field} FROM #{main_table} WHERE id=(last_insert_rowid()))"
89
              end
90
            end
91
          end
92
          queries = []      
93
          inserts.each do |table, values|
94
            queries.push "INSERT INTO #{table}(#{fields[table].join(',')}) VALUES (#{inserts[table].join(',')});"
95
          end
96
          # Connect to the database
97
          dbe = MIDB::API::Dbengine.new(@engine.config, @db)
98
          dblink = dbe.connect()
99
          results = []
100
          rid = nil
101
          # Find the ID to return in the response (only for the first query)
102
          queries.each do |q|
103
            results.push dbe.query(dblink, q)
104
            if @engine.config["dbengine"] == :mysql
105
              rid ||= dbe.extract(dbe.query(dblink, "SELECT id FROM #{main_table} WHERE id=(SELECT LAST_INSERT_ID());"), "id")
106
            else
107
              rid ||= dbe.extract(dbe.query(dblink, "SELECT id FROM #{main_table} WHERE id=(last_insert_rowid());"), "id")
108
            end
109
          end
110
          @engine.http_status = "201 Created"
111
          resp = {status: "201 created", id: rid}
112
        end
113
        return resp
114
      end
115
116
      # Update an already existing resource
117
      #
118
      # @param id [Fixnum] ID to alter
119
      # @param data [String] HTTP query string
120
      def put(id, data)
121
        jss = self.get_structure() # For referencing purposes
122
123
        input = self.query_to_hash(data)
124
        bad_request = false
125
        resp = nil
126
        input.each do |key, value|
127
          # Check if we have it on the structure too
128
          unless jss.has_key? key
129
            resp = MIDB::Interface::Server.json_error(400, "Bad Request - Wrong argument #{key}")
130
            @engine.http_status = 400
131
            bad_request = true
132
          end
133
        end
134
135
        # Check if the ID exists
136
        db = MIDB::API::Dbengine.new(@engine.config, @db)
137
        dbc = db.connect()
138
        dbq = db.query(dbc, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]} WHERE id=#{id};")
139
        unless db.length(dbq) > 0
140
          resp = MIDB::Interface::Server.json_error(404, "ID not found")
141
          @engine.http_status = 404
142
          bad_request = true
143
        end
144
        
145
        # Update the values if we have a good request
146
        unless bad_request
147
          fields = Hash.new
148
          inserts = Hash.new
149
          where_clause = Hash.new
150
          main_table = self.get_structure.values[0].split('/')[0]
151
          where_clause[main_table] = "id=#{id}"
152
          input.each do |key, value|
153
            struct = jss[key]
154
            table = struct.split("/")[0]
155
            inserts[table] ||= []
156
            fields[table] ||= []
157
            inserts[table].push "\"" + value + "\""
158
            fields[table].push struct.split("/")[1]
159
            if struct.split("/").length > 2
160
              match = struct.split("/")[2]
161
              matching_field = match.split("->")[0]
162
              row_field = match.split("->")[1]
163
              where_clause[table] = "#{matching_field}=(SELECT #{row_field} FROM #{main_table} WHERE #{where_clause[main_table]});"
164
            end
165
          end
166
          queries = []
167
          updates = Hash.new
168
          # Turn it into a hash
169
          inserts.each do |table, values|
170
            updates[table] ||= Hash.new
171
            updates[table] = Hash[fields[table].zip(inserts[table])]
172
            query = "UPDATE #{table} SET "
173
            updates[table].each do |f, v|
174
              query = query + "#{f}=#{v} "
175
            end
176
            queries.push query + "WHERE #{where_clause[table]};"
177
          end
178
          # Run the queries
179
          results = []
180
          queries.each do |q|
181
            results.push db.query(dbc, q)
182
          end
183
          @engine.http_status = "200 OK"
184
          resp = {status: "200 OK"}
185
        end
186
        return resp
187
      end
188
189
      # Delete a resource
190
      #
191
      # @param id [Fixnum] ID to delete
192
      def delete(id)
193
        # Check if the ID exists
194
        db = MIDB::API::Dbengine.new(@engine.config, @db)
195
        dbc = db.connect()
196
        dbq = db.query(dbc, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]} WHERE id=#{id};")
197
        if not db.length(dbq) > 0
198
          resp = MIDB::Interface::Server.json_error(404, "ID not found").to_json
199
          @engine.http_status = 404
200
          bad_request = true
201
        else
202
          # ID Found, so let's delete it. (including linked resources!)
203
          jss = self.get_structure() # Referencing
204
205
          where_clause = {}
206
          tables = []
207
          main_table = jss.values[0].split('/')[0]
208
          where_clause[main_table] = "id=#{id}"
209
210
          jss.each do |k, v|
211
            table = v.split("/")[0]
212
            tables.push table unless tables.include? table
213
            # Check if it's a linked resource, generate WHERE clause accordingly
214
            if v.split("/").length > 2
215
              match = v.split("/")[2]
216
              matching_field = match.split("->")[0]
217
              row_field = match.split("->")[1]
218
              # We have to run the subquery now because it'll be deleted later!
219
              subq = "SELECT #{row_field} FROM #{main_table} WHERE #{where_clause[main_table]};"
220
              res = db.query(dbc, subq)
221
              subqres = db.extract(res, row_field)
222
              where_clause[table] ||= "#{matching_field}=#{subqres}"
223
            else
224
              # Normal WHERE clause
225
              where_clause[table] ||= "id=#{id}"
226
            end
227
          end
228
229
          # Generate and run queries
230
          results = []
231
          tables.each do |tb|
232
            query = "DELETE FROM #{tb} WHERE #{where_clause[tb]};"
233
            results.push db.query(dbc, query)
234
          end
235
          @engine.http_status = "200 OK"
236
          resp = {status: "200 OK"}
237
        end
238
        return resp
239
      end
240
241
      # Get an entry with a given id.
242
      #
243
      # @param id [Fixnum] ID of the entry
244
      def get_entries(id)
245
        jso = Hash.new()
246
247
        dbe = MIDB::API::Dbengine.new(@engine.config, @db)
248
        dblink = dbe.connect()
249
        rows = dbe.query(dblink, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]} WHERE id=#{id};")
250
        if rows == false
251
          return MIDB::Interface::Server.json_error(400, "Bad Request")
252
        end
253
        if dbe.length(rows) > 0
254
          rows.each do |row|
255
            jso[row["id"]] = self.get_structure
256
            self.get_structure.each do |name, dbi|
257
              table = dbi.split("/")[0]
258
              field = dbi.split("/")[1]
259
              # Must-match relations ("table2/field/table2-field->row-field")
260
              if dbi.split("/").length > 2
261
                match = dbi.split("/")[2]
262
                matching_field = match.split("->")[0]
263
                row_field = match.split("->")[1]
264
                query = dbe.query(dblink, "SELECT #{field} FROM #{table} WHERE #{matching_field}=#{row[row_field]};")
265
              else
266
                query = dbe.query(dblink, "SELECT #{field} from #{table} WHERE id=#{row['id']};")
267
              end
268
              if query == false
269
                return MIDB::Interface::Server.json_error(400, "Bad Request")
270
              end
271
              jso[row["id"]][name] = dbe.length(query) > 0 ? dbe.extract(query,field) : "unknown"
272
              jso[row["id"]][name] = @hooks.format_field(name, jso[row["id"]][name])
273
            end
274
          end
275
          @engine.http_status = "200 OK"
276
        else
277
          @engine.http_status = "404 Not Found"
278
          jso = MIDB::Interface::Server.json_error(404, "Not Found")
279
        end
280
        return jso
281
282
      end
283
284
      # Get all the entries from the database
285
      def get_all_entries()
286
        jso = Hash.new()
287
     
288
        # Connect to database
289
        dbe = MIDB::API::Dbengine.new(@engine.config, @db)
290
        dblink = dbe.connect()
291
        rows = dbe.query(dblink, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]};")
292
        if rows == false
293
          return MIDB::Interface::Server.json_error(400, "Bad Request")
294
        end
295
        # Iterate over all rows of this table
296
        rows.each do |row|
297
          jso[row["id"]] = self.get_structure
298
          self.get_structure.each do |name, dbi|
299
            table = dbi.split("/")[0]
300
            field = dbi.split("/")[1]
301
            # Must-match relations ("table2/field/table2-field->row-field")
302
            if dbi.split("/").length > 2
303
              match = dbi.split("/")[2]
304
              matching_field = match.split("->")[0]
305
              row_field = match.split("->")[1]
306
              query = dbe.query(dblink, "SELECT #{field} FROM #{table} WHERE #{matching_field}=#{row[row_field]};")
307
            else
308
              query = dbe.query(dblink, "SELECT #{field} from #{table} WHERE id=#{row['id']};")
309
            end
310
            if query == false
311
              return MIDB::Interface::Server.json_error(400, "Bad Request")
312
            end
313
            jso[row["id"]][name] = dbe.length(query) > 0 ? dbe.extract(query,field) : "unknown"
314
            jso[row["id"]][name] = @hooks.format_field(name, jso[row["id"]][name])
315
          end
316
        end
317
        @hooks.after_get_all_entries(dbe.length(rows))
318
        return jso
319
      end
320
321
322
      # Get all the entries from the database belonging to a column
323
      def get_column_entries(column)
324
        jso = Hash.new() 
325
        jss = self.get_structure()
326
        db_column = nil
327
        # Is the column recognized?
328
        if jss.has_key? column then
329
          db_column = jss[column].split("/")[1]
330
        else
331
          return MIDB::Interface::Server.json_error(400, "Bad Request")
332
        end
333
  
334
        # Connect to database
335
        dbe = MIDB::API::Dbengine.new(@engine.config, @db)
336
        dblink = dbe.connect()
337
        rows = dbe.query(dblink, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]};")
338
        if rows == false
339
          return MIDB::Interface::Server.json_error(400, "Bad Request")
340
        end
341
        # Iterate over all rows of this table
342
        rows.each do |row|
343
344
          name = column
345
          dbi = jss[name]
346
          table = dbi.split("/")[0]
347
          field = dbi.split("/")[1]
348
          # Must-match relations ("table2/field/table2-field->row-field")
349
          if dbi.split("/").length > 2
350
            match = dbi.split("/")[2]
351
            matching_field = match.split("->")[0]
352
            row_field = match.split("->")[1]
353
            query = dbe.query(dblink, "SELECT #{field} FROM #{table} WHERE #{matching_field}=#{row[row_field]};")
354
          else
355
            query = dbe.query(dblink, "SELECT #{field} from #{table} WHERE id=#{row['id']};")
356
          end
357
          if query == false
358
            return MIDB::Interface::Server.json_error(400, "Bad Request")
359
          end
360
          jso[row["id"]] = {}
361
          jso[row["id"]][name] = dbe.length(query) > 0 ? dbe.extract(query,field) : "unknown"
362
          jso[row["id"]][name] = @hooks.format_field(name, jso[row["id"]][name])
363
        end
364
        @hooks.after_get_all_entries(dbe.length(rows))
365
        return jso
366
      end
367
368
      # Get all the entries from the database belonging to a column matching a pattern
369
      def get_matching_rows(column, pattern)
370
        jso = Hash.new() 
371
        jss = self.get_structure()
372
        db_column = nil
373
        # Is the column recognized?
374
        if jss.has_key? column then
375
          db_column = jss[column].split("/")[1]
376
        else
377
          return MIDB::Interface::Server.json_error(400, "Bad Request")
378
        end
379
  
380
        # Connect to database
381
        dbe = MIDB::API::Dbengine.new(@engine.config, @db)
382
        dblink = dbe.connect()
383
        rows = dbe.query(dblink, "SELECT * FROM #{self.get_structure.values[0].split('/')[0]};")
384
        if rows == false
385
          return MIDB::Interface::Server.json_error(400, "Bad Request")
386
        end
387
        # Iterate over all rows of this table
388
        rows.each do |row|
389
          # Does this row match?
390
          bufd = jss[column]
391
          b_table = bufd.split("/")[0]
392
          b_field = bufd.split("/")[1]
393
          # The column is in another table, let's find it
394
          if bufd.split("/").length > 2    
395
            b_match = bufd.split("/")[2]
396
            b_m_field = b_match.split("->")[0]
397
            b_r_field = b_match.split("->")[1]
398
399
            bquery = dbe.query(dblink, "SELECT #{b_field} FROM #{b_table} WHERE (#{b_m_field}=#{row[b_r_field]} AND #{db_column} LIKE '%#{pattern}%');")
400
          else
401
            # It's in the same main table, let's see if it matches
402
            bquery = dbe.query(dblink, "SELECT #{b_field} FROM #{b_table} WHERE (id=#{row['id']} AND #{db_column} LIKE '%#{pattern}%');")
403
          end
404
405
          # Unless the query has been successful (thus this row matches), skip to the next row
406
          unless dbe.length(bquery) > 0
407
            next
408
          end
409
410
          jso[row["id"]] = self.get_structure
411
412
          self.get_structure.each do |name, dbi|
413
            table = dbi.split("/")[0]
414
            field = dbi.split("/")[1]
415
            # Must-match relations ("table2/field/table2-field->row-field")
416
            if dbi.split("/").length > 2
417
              match = dbi.split("/")[2]
418
              matching_field = match.split("->")[0]
419
              row_field = match.split("->")[1]
420
              query = dbe.query(dblink, "SELECT #{field} FROM #{table} WHERE #{matching_field}=#{row[row_field]};")
421
            else
422
              query = dbe.query(dblink, "SELECT #{field} from #{table} WHERE id=#{row['id']};")
423
            end
424
            if query == false
425
              next
426
            end
427
            jso[row["id"]][name] = dbe.length(query) > 0 ? dbe.extract(query,field) : "unknown"
428
            jso[row["id"]][name] = @hooks.format_field(name, jso[row["id"]][name])
429
          end
430
        end
431
        @hooks.after_get_all_entries(dbe.length(rows))
432
        return jso
433
      end
434
    end
435
  end
436
end
437