sqliteUtilities_mod sourceΒΆ

  1module sqliteUtilities_mod
  2  ! MODULE sqliteUtilities_mod (prefix='sqlu' category='3. Observation input/output')
  3  !
  4  !:Purpose: A place to collect utilities for SQLite files.
  5  !
  6  use fSQLite
  7  use clibInterfaces_mod
  8  use obsSpaceData_mod
  9  use midasMpi_mod
 10  use utilities_mod
 11  use mathPhysConstants_mod
 12
 13  implicit none
 14  save
 15  private
 16  public :: sqlu_sqlColumnExists, sqlu_sqlTableExists, sqlu_getSqlColumnNames
 17  public :: sqlu_query, sqlu_handleError
 18  public :: sqlu_getColumnValuesNum, sqlu_getColumnValuesDateStr, sqlu_getColumnValuesChar
 19  public :: sqlu_getInitialIdObsData
 20
 21  ! Arrays used to match SQLite column names with obsSpaceData column names
 22  integer, parameter :: lenSqlName = 60
 23
 24contains
 25   
 26  !--------------------------------------------------------------------------
 27  ! sqlu_sqlColumnExists
 28  !--------------------------------------------------------------------------
 29  function sqlu_sqlColumnExists(fileName, tableName, columnName) result(columnExists)
 30    !
 31    !:Purpose: Check if a column exists in the sqlite file/table
 32    !
 33    implicit none
 34
 35    ! Arguments:
 36    character(len=*),              intent(in)  :: fileName
 37    character(len=*),              intent(in)  :: tableName
 38    character(len=*),              intent(in)  :: columnName
 39    ! Result:
 40    logical                                    :: columnExists
 41
 42    ! Locals:
 43    integer                     :: ierr
 44    character(len=3000)         :: query, sqliteOutput
 45    character(len=lenSqlName)   :: upperColumnName
 46    type(fSQL_STATUS)           :: stat ! sqlite error status
 47    type(fSQL_DATABASE)         :: db   ! sqlite file handle
 48    logical, parameter          :: debug = .false.
 49    character(len=*), parameter :: myName = 'sqlu_sqlColumnExists'
 50    
 51    ! open the SQLite file
 52    call fSQL_open( db, trim(fileName), status=stat )
 53    if ( fSQL_error(stat) /= FSQL_OK ) then
 54      call utl_abort( myName//fSQL_errmsg(stat) )
 55    end if
 56
 57    upperColumnName = trim(columnName)
 58    ierr = clib_toUpper(upperColumnName)
 59
 60    query = "select count(*) from pragma_table_info('" // &
 61            trim(tableName) // "') where upper(name)='" // trim(upperColumnName) // "';"
 62    if (debug) write(*,*) myName//': query = ', trim(query)
 63
 64    sqliteOutput = sqlu_query(db,trim(query))
 65    if (debug) write(*,*) myName//': output = XXX' // trim(sqliteOutput) // 'XXX'
 66    columnExists = (trim(sqliteOutput) /= '0')
 67
 68    ! close the sqlite file
 69    call fSQL_close( db, stat )
 70
 71  end function sqlu_sqlColumnExists
 72
 73  !--------------------------------------------------------------------------
 74  ! sqlu_sqlTableExists
 75  !--------------------------------------------------------------------------
 76  function sqlu_sqlTableExists(fileName, tableName) result(tableExists)
 77    !
 78    !:Purpose: Check if a table exists in the sqlite file
 79    !
 80    implicit none
 81
 82    ! Arguments:
 83    character(len=*),              intent(in)  :: fileName
 84    character(len=*),              intent(in)  :: tableName
 85    ! Result:
 86    logical                                    :: tableExists
 87
 88    ! Locals:
 89    integer                     :: ierr
 90    logical                     :: finished
 91    character(len=3000)         :: query, sqliteOutput
 92    character(len=lenSqlName)   :: upperTableName
 93    type(fSQL_STATUS)           :: stat ! sqlite error status
 94    type(fSQL_DATABASE)         :: db   ! sqlite file handle
 95    type(fSQL_STATEMENT)        :: stmt ! precompiled sqlite statements
 96    logical, parameter          :: debug = .false.
 97    character(len=*), parameter :: myName = 'sqlu_sqlTableExists'
 98
 99    ! open the sqlite file
100    call fSQL_open( db, trim(fileName), status=stat )
101    if ( fSQL_error(stat) /= FSQL_OK ) then
102      call utl_abort( myName//': fSQL_open '//fSQL_errmsg(stat) )
103    end if
104
105    upperTableName = trim(tableName)
106    ierr = clib_toUpper(upperTableName)
107
108    query = "select upper(name) as uppername from sqlite_master where " // &
109            "type='table' and uppername='" // trim(upperTableName) // "';"
110    if (debug) write(*,*) myName//': query = ', trim(query)
111
112    call fSQL_prepare( db, trim(query), stmt, stat)
113    finished = .false.
114    call fSQL_get_row( stmt, finished )
115    call fSQL_get_column( stmt, COL_INDEX = 1, CHAR_VAR = sqliteOutput )
116    call fSQL_get_row( stmt, finished )
117    call fSQL_finalize( stmt )
118    if (debug) write(*,*) myName//': output = XXX' // trim(sqliteOutput) // 'XXX'
119    tableExists = (trim(sqliteOutput) == trim(upperTableName))
120
121    ! close the sqlite file
122    call fSQL_close( db, stat ) 
123
124  end function sqlu_sqlTableExists
125
126  !--------------------------------------------------------------------------
127  ! sqlu_getSqlColumnNames
128  !--------------------------------------------------------------------------
129  subroutine sqlu_getSqlColumnNames(sqlColumnNames, fileName, tableName, dataType)
130    !
131    !:Purpose: Read the column names in the sqlite file for the specified table.
132    !
133    implicit none
134
135    ! Arguments:
136    character(len=*), allocatable, intent(out) :: sqlColumnNames(:)
137    character(len=*),              intent(in)  :: fileName
138    character(len=*),              intent(in)  :: tableName
139    character(len=*),              intent(in)  :: dataType
140
141    ! Locals:
142    integer :: numRows, numColumns, rowIndex, ierr
143    character(len=100), allocatable :: charValues(:,:)
144    character(len=200)       :: dataTypeCriteria
145    character(len=3000)      :: query
146    type(fSQL_STATUS)        :: stat ! sqlite error status
147    type(fSQL_DATABASE)      :: db   ! sqlite file handle
148    type(fSQL_STATEMENT)     :: stmt ! precompiled sqlite statements
149    character(len=*), parameter :: myName = 'sqlu_getSqlColumnNames'
150
151    ! open the sqlite file
152    call fSQL_open( db, trim(fileName), status=stat )
153    if ( fSQL_error(stat) /= FSQL_OK ) then
154      call utl_abort( myName//': fSQL_open '//fSQL_errmsg(stat) )
155    end if
156
157    ! read the column names
158    if (trim(dataType) == 'varchar') then
159      dataTypeCriteria = 'substr(type,1,7)="varchar"'
160    else if (trim(dataType) == 'numeric') then
161      dataTypeCriteria = 'type="real" or type="REAL" or type="double" or ' // &
162                         'type="DOUBLE" or type="integer" or type="INTEGER" or ' // &
163                         'type="INT" or type=""'
164    else if (trim(dataType) /= 'all') then
165      call utl_abort( myName//': invalid dataType = ' // trim(dataType) )
166    end if
167    if (trim(dataType) == 'all') then
168      query = 'select name from pragma_table_info("' // trim(tableName) // '");'
169    else
170      query = 'select name from pragma_table_info("' // trim(tableName) // &
171              '") where ' // trim(dataTypeCriteria) // ' ;'
172    end if
173    call fSQL_prepare( db, trim(query) , stmt, stat )
174    call fSQL_get_many( stmt, nrows=numRows, ncols=numColumns, &
175                        mode=FSQL_CHAR, status=stat )
176    if ( fSQL_error(stat) /= FSQL_OK ) then
177      call utl_abort( myName//': problem with fSQL_get_many '//fSQL_errmsg(stat))
178    end if
179    allocate( charValues(numRows, numColumns) )
180    call fSQL_fill_matrix( stmt, charValues )
181
182    ! copy to output array and ensure they are upper case
183    allocate( sqlColumnNames(numRows) )
184    do rowIndex = 1, numRows
185      sqlColumnNames(rowIndex) = charValues(rowIndex,1)
186      ierr = clib_toUpper(sqlColumnNames(rowIndex))
187    end do
188    deallocate( charValues )
189
190    ! clean up and close the sqlite file
191    call fSQL_free_mem( stmt )
192    call fSQL_finalize( stmt )
193    call fSQL_close( db, stat ) 
194
195  end subroutine sqlu_getSqlColumnNames
196
197  !--------------------------------------------------------------------------
198  ! sqlu_getColumnValuesNum
199  !--------------------------------------------------------------------------
200  subroutine sqlu_getColumnValuesNum(columnValues, fileName, tableName, &
201                                     sqlColumnNames, extraQuery_opt)
202    !
203    !:Purpose: Read the column values from sqlite file for the specified table
204    !           and column names.
205    !
206    implicit none
207
208    ! Arguments:
209    real(8), allocatable,       intent(out) :: columnValues(:,:)
210    character(len=*),           intent(in)  :: sqlColumnNames(:)
211    character(len=*),           intent(in)  :: fileName
212    character(len=*),           intent(in)  :: tableName
213    character(len=*), optional, intent(in)  :: extraQuery_opt
214
215    ! Locals:
216    integer :: numRows, numColumns, columnIndex
217    character(len=3000)       :: query, extraQuery
218    type(fSQL_STATUS)         :: stat ! sqlite error status
219    type(fSQL_DATABASE)       :: db   ! sqlite file handle
220    type(fSQL_STATEMENT)      :: stmt ! precompiled sqlite statements
221
222    if (present(extraQuery_opt)) then
223      extraQuery = trim(extraQuery_opt)
224    else
225      extraQuery = ''
226    end if
227
228    ! open the sqlite file
229    call fSQL_open( db, trim(fileName), status=stat )
230    if ( fSQL_error(stat) /= FSQL_OK ) then
231      write(*,*) 'sqlu_getColumnValuesNum: fSQL_open: ', fSQL_errmsg(stat)
232      call utl_abort( 'sqlu_getColumnValuesNum: fSQL_open' )
233    end if
234
235    ! build the sqlite query
236    query = 'select'
237    numColumns = size(sqlColumnNames)
238    do columnIndex = 1, numColumns
239      query = trim(query) // ' ' // trim(sqlColumnNames(columnIndex))
240      if (columnIndex < numColumns) query = trim(query) // ','
241    end do
242    query = trim(query) // ' from ' // trim(tableName)
243    query = trim(query) // ' ' // trim(extraQuery) // ';'
244    write(*,*) 'sqlu_getColumnValuesNum: query ---> ', trim(query)
245
246    ! read the values from the file
247    call fSQL_prepare( db, trim(query) , stmt, status=stat )
248    call fSQL_get_many( stmt, nrows=numRows, ncols=numColumns, mode=FSQL_REAL8, &
249                        real8_missing=MPC_missingValue_R8, status=stat )
250    if ( fSQL_error(stat) /= FSQL_OK ) then
251      write(*,*) 'sqlu_getColumnValuesNum: fSQL_get_many: ', fSQL_errmsg(stat)
252      call utl_abort('sqlu_getColumnValuesNum: problem with fSQL_get_many')
253    end if
254    write(*,*) 'sqlu_getColumnValuesNum: numRows = ', numRows, ', numColumns = ', numColumns
255    allocate( columnValues(numRows, numColumns) )
256    columnValues(:,:) = 0.0d0
257    call fSQL_fill_matrix( stmt, columnValues )
258
259    ! close the sqlite file
260    call fSQL_free_mem( stmt )
261    call fSQL_finalize( stmt )
262    call fSQL_close( db, stat ) 
263
264  end subroutine sqlu_getColumnValuesNum
265
266  !--------------------------------------------------------------------------
267  ! sqlu_getColumnValuesChar
268  !--------------------------------------------------------------------------
269  subroutine sqlu_getColumnValuesChar(columnValues, fileName, tableName, &
270                                      sqlColumnNames)
271    !
272    !:Purpose: Read the column values from sqlite file for the specified table
273    !           and column names.
274    !
275    implicit none
276
277    ! Arguments:
278    character(len=50), allocatable, intent(out) :: columnValues(:,:)
279    character(len=*),               intent(in)  :: sqlColumnNames(:)
280    character(len=*),               intent(in)  :: fileName
281    character(len=*),               intent(in)  :: tableName
282
283    ! Locals:
284    integer :: numRows, numColumns, columnIndex
285    character(len=3000)      :: query
286    type(fSQL_STATUS)        :: stat ! sqlite error status
287    type(fSQL_DATABASE)      :: db   ! sqlite file handle
288    type(fSQL_STATEMENT)     :: stmt ! precompiled sqlite statements
289
290    ! open the sqlite file
291    call fSQL_open( db, trim(fileName), status=stat )
292    if ( fSQL_error(stat) /= FSQL_OK ) then
293      write(*,*) 'sqlu_getColumnValuesChar: fSQL_open: ', fSQL_errmsg(stat)
294      call utl_abort( 'sqlu_getColumnValuesChar: fSQL_open' )
295    end if
296
297    ! build the sqlite query
298    query = 'select'
299    numColumns = size(sqlColumnNames)
300    do columnIndex = 1, numColumns
301      query = trim(query) // ' ' // trim(sqlColumnNames(columnIndex))
302      if (columnIndex < numColumns) query = trim(query) // ','
303    end do
304    query = trim(query) // ' from ' // trim(tableName) // ';'
305    write(*,*) 'sqlu_getColumnValuesChar: query ---> ', trim(query)
306
307    ! read the values from the file
308    call fSQL_prepare( db, trim(query), stmt, status=stat )
309    call fSQL_get_many( stmt, nrows=numRows, ncols=numColumns, &
310                        mode=FSQL_CHAR, status=stat )
311    if ( fSQL_error(stat) /= FSQL_OK ) then
312      write(*,*) 'sqlu_getColumnValuesChar: fSQL_get_many: ', fSQL_errmsg(stat)
313      call utl_abort('sqlu_getColumnValuesChar: problem with fSQL_get_many')
314    end if
315    write(*,*) 'sqlu_getColumnValuesChar: numRows = ', numRows, ', numColumns = ', numColumns
316    allocate( columnValues(numRows, numColumns) )
317    call fSQL_fill_matrix( stmt, columnValues )
318
319    ! close the sqlite file
320    call fSQL_free_mem( stmt )
321    call fSQL_finalize( stmt )
322    call fSQL_close( db, stat ) 
323
324  end subroutine sqlu_getColumnValuesChar
325
326  !--------------------------------------------------------------------------
327  ! sqlu_getColumnValuesDateStr
328  !--------------------------------------------------------------------------
329  subroutine sqlu_getColumnValuesDateStr(columnDateValues, columnTimeValues, fileName, &
330                                         tableName, sqlColumnName)
331    !
332    !:Purpose: Read the column values from sqlite file for the specified table
333    !           and column names.
334    !
335    implicit none
336
337    ! Arguments:
338    integer, allocatable, intent(out) :: columnDateValues(:)
339    integer, allocatable, intent(out) :: columnTimeValues(:)
340    character(len=*),     intent(in)  :: sqlColumnName
341    character(len=*),     intent(in)  :: fileName
342    character(len=*),     intent(in)  :: tableName
343
344    ! Locals:
345    integer              :: numRows, numColumns, rowIndex
346    character(len=20), allocatable :: columnValuesStr(:,:)
347    character(len=3000)  :: query
348    type(fSQL_STATUS)    :: stat ! sqlite error status
349    type(fSQL_DATABASE)  :: db   ! sqlite file handle
350    type(fSQL_STATEMENT) :: stmt ! precompiled sqlite statements
351
352    ! open the sqlite file
353    call fSQL_open( db, trim(fileName), status=stat )
354    if ( fSQL_error(stat) /= FSQL_OK ) then
355      write(*,*) 'sqlu_getColumnValuesDateStr: fSQL_open: ', fSQL_errmsg(stat)
356      call utl_abort( 'sqlu_getColumnValuesDateStr: fSQL_open' )
357    end if
358
359    ! Get the date and time
360
361    ! build the sqlite query
362    query = "select strftime('%Y%m%d'," // trim(sqlColumnName) // &
363            "), strftime('%H%M'," // trim(sqlColumnName) // ") " // &
364            "from " // trim(tableName) // ";"
365    write(*,*) 'sqlu_getColumnValuesDateStr: query ---> ', trim(query)
366
367    ! read the values from the file
368    call fSQL_prepare( db, trim(query), stmt, status=stat )
369    call fSQL_get_many( stmt, nrows=numRows, ncols=numColumns, &
370                        mode=FSQL_CHAR, status=stat )
371    if ( fSQL_error(stat) /= FSQL_OK ) then
372      write(*,*) 'sqlu_getColumnValuesDateStr: fSQL_get_many: ', fSQL_errmsg(stat)
373      call utl_abort('sqlu_getColumnValuesDateStr: problem with fSQL_get_many')
374    end if
375    write(*,*) 'sqlu_getColumnValuesDateStr: numRows = ', numRows, ', numColumns = ', numColumns
376    allocate( columnValuesStr(numRows,2) )
377    call fSQL_fill_matrix( stmt, columnValuesStr )
378    allocate( columnDateValues(numRows) )
379    allocate( columnTimeValues(numRows) )
380    do rowIndex = 1, numRows
381      read(columnValuesStr(rowIndex,1),*) columnDateValues(rowIndex)
382      read(columnValuesStr(rowIndex,2),*) columnTimeValues(rowIndex)
383    end do
384
385    deallocate(columnValuesStr)
386
387    ! close the sqlite file
388    call fSQL_free_mem( stmt )
389    call fSQL_finalize( stmt )
390    call fSQL_close( db, stat ) 
391
392  end subroutine sqlu_getColumnValuesDateStr
393
394  !--------------------------------------------------------------------------
395  ! sqlu_getInitialIdObsData
396  !--------------------------------------------------------------------------
397  subroutine sqlu_getInitialIdObsData(obsDat, obsFamily, idObs, idData, codeTypeList_opt)
398    !
399    !:Purpose: Compute initial value for idObs and idData that will ensure
400    !           unique values over all mpi tasks
401    !
402    implicit none
403
404    ! Arguments:
405    type(struct_obs),  intent(inout) :: obsdat
406    character(len=*),  intent(in)    :: obsFamily    
407    integer         ,  intent(out)   :: idObs
408    integer         ,  intent(out)   :: idData
409    integer, optional, intent(in)    :: codeTypeList_opt(:)
410
411    ! Locals:
412    integer                :: headerIndex, numHeader, numBody, codeType, ierr
413    integer, allocatable   :: allNumHeader(:), allNumBody(:)
414
415    numHeader = 0
416    numBody = 0
417    call obs_set_current_header_list(obsdat, obsFamily)
418    HEADERCOUNT: do
419      headerIndex = obs_getHeaderIndex(obsdat)
420      if (headerIndex < 0) exit HEADERCOUNT
421      if (present(codeTypeList_opt)) then
422        codeType  = obs_headElem_i(obsdat, OBS_ITY, headerIndex)
423        if (all(codeTypeList_opt(:) /= codeType)) cycle HEADERCOUNT
424      end if
425      numHeader = numHeader + 1
426      numBody = numBody + obs_headElem_i(obsdat, OBS_NLV, headerIndex)
427    end do HEADERCOUNT
428    allocate(allNumHeader(mmpi_nprocs))
429    allocate(allNumBody(mmpi_nprocs))
430    call rpn_comm_allgather(numHeader,1,'mpi_integer',       &
431                            allNumHeader,1,'mpi_integer','GRID',ierr)
432    call rpn_comm_allgather(numBody,1,'mpi_integer',       &
433                            allNumBody,1,'mpi_integer','GRID',ierr)
434    if (mmpi_myid > 0) then
435      idObs = sum(allNumHeader(1:mmpi_myid))
436      idData = sum(allNumBody(1:mmpi_myid))
437    else
438      idObs = 0
439      idData = 0
440    end if
441    deallocate(allNumHeader)
442    deallocate(allNumBody)
443
444  end subroutine sqlu_getInitialIdObsData
445
446  !--------------------------------------------------------------------------
447  ! sqlu_query
448  !--------------------------------------------------------------------------
449  function sqlu_query(db,query)
450    !
451    !:Purpose: To create a query to read an SQLite file
452    !
453    implicit none
454
455    ! Arguments:
456    type(fSQL_DATABASE), intent(inout)  :: db    ! type handle for  SQLIte file
457    character(len=*),    intent(in)     :: query
458    ! Result:
459    character(len=256)   :: sqlu_query
460
461    ! Locals:
462    character(len=256)   :: result
463    logical :: finished
464    type(fSQL_STATEMENT) :: stmt !  prepared statement for  SQLite
465    type(fSQL_STATUS)    :: stat !type error status
466
467    result=''
468    call fSQL_prepare(db, trim(query), stmt, stat)
469    if (fSQL_error(stat) /= FSQL_OK) call sqlu_handleError(stat,'fSQL_prepare: ')
470    finished=.false.
471    call fSQL_get_row(stmt, finished)
472
473    ! Put result of query into variable
474    call fSQL_get_column(stmt, COL_INDEX = 1, char_var = result)
475    call fSQL_get_row(stmt, finished)
476    if (.not. finished) write(*,*) ' SQL QUERY ---> QUERY RETURNS MORE THAN ONE ROW...  '
477    call fSQL_finalize(stmt)
478    sqlu_query = trim(result)
479
480  end function sqlu_query
481
482  !--------------------------------------------------------------------------
483  ! sqlu_handleError
484  !--------------------------------------------------------------------------
485  subroutine sqlu_handleError(stat, message)
486    implicit none
487
488    ! Arguments:
489    type(FSQL_STATUS), intent(in) :: stat
490    character(len=*),  intent(in) :: message
491
492    write(*,*) message, fSQL_errmsg(stat)
493    call utl_abort(trim(message))
494
495  end subroutine sqlu_handleError
496
497end module sqliteUtilities_mod