rivet/packages/dio/formatters.tcl (235 lines of code) (raw):

# formatters.tcl -- connector for tdbc, the Tcl database abstraction layer # # Copyright 2024 The Apache Software Foundation # # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. namespace eval ::DIO::formatters { # ::itcl::class FieldFormatter # # we devolve the role of special field formatter to this # class. By design this is more sensible with respect to # the current approach of having such method in subclasses # because it allows to reuse the functionality of this # class in other DBMS connector. # this class must be subclassed for each dbms or SQL dialect ::itcl::class RootFormatter { private variable special_fields [dict create] public proc quote {a_string} { regsub -all {'} $a_string {\'} a_string return $a_string } public method register {table_name field_name ftype} { dict set special_fields $table_name $field_name $ftype } public method build {table_name field_name val convert_to} { if {[dict exists $special_fields $table_name $field_name]} { set field_type [dict get $special_fields $table_name $field_name] if {[catch { set field_value [$this $field_type $field_name $val $convert_to] } e einfo]} { set field_value "'[quote $val]'" } return $field_value } else { return "'[quote $val]'" } } } ; ## ::itcl::class FieldFormatter ::itcl::class Mysql { inherit RootFormatter public method DATE {field_name val convert_to} { set secs [clock scan $val] set my_val [clock format $secs -format {%Y-%m-%d}] return "DATE_FORMAT('$my_val','%Y-%m-%d')" } public method DATETIME {field_name val convert_to} { set secs [clock scan $val] set my_val [clock format $secs -format {%Y-%m-%d %T}] return "DATE_FORMAT('$my_val','%Y-%m-%d %T')" } public method NOW {field_name val convert_to} { # we try to be coherent with the original purpose of this method whose # goal is endow the class with a uniform way to handle timestamps. # E.g.: Package session expects this case to return a timestamp in seconds # so that differences with timestamps returned by [clock seconds] # can be done and session expirations are computed consistently. # (Bug #53703) switch $convert_to { SECS { if {[::string compare $val "now"] == 0} { # set secs [clock seconds] # set my_val [clock format $secs -format {%Y%m%d%H%M%S}] # return $my_val return [clock seconds] } else { return "UNIX_TIMESTAMP($field_name)" } } default { if {[::string compare $val, "now"] == 0} { set secs [clock seconds] } else { set secs [clock scan $val] } # this is kind of going back and forth from the same # format, #set my_val [clock format $secs -format {%Y-%m-%d %T}] return "FROM_UNIXTIME('$secs')" } } } public method NULL {field_name val convert_to} { if {[::string toupper $val] == "NULL"} { return $val } else { return "'[quote $val]'" } } } ::itcl::class Sqlite { inherit RootFormatter # # quote - given a string, return the same string with any single # quote characters preceded by a backslash # method quote {a_string} { regsub -all {'} $a_string {''} a_string return $a_string } public method DATE {field_name val convert_to} { set secs [clock scan $val] set my_val [clock format $secs -format {%Y-%m-%d}] return "date('$my_val')" } public method DATETIME {field_name val convert_to} { set secs [clock scan $val] set my_val [clock format $secs -format {%Y-%m-%d %T}] return "datetime('$my_val')" } public method NOW {field_name val convert_to} { switch $convert_to { # we try to be coherent with the original purpose of this method whose # goal is to provide a uniform way to handle timestamps. # E.g.: Package session expects this case to return a timestamp in seconds # so that differences with timestamps returned by [clock seconds] # can be done and session expirations are computed consistently. # (Bug #53703) SECS { if {[::string compare $val "now"] == 0} { # set secs [clock seconds] # set my_val [clock format $secs -format "%Y%m%d%H%M%S"] return [clock seconds] } else { # the numbers of seconds must be returned as 'utc' to # be compared with values returned by [clock seconds] return "strftime('%s',$field_name,'utc')" } } default { if {[::string compare $val, "now"] == 0} { set secs [clock seconds] } else { set secs [clock scan $val] } set my_val [clock format $secs -format {%Y-%m-%d %T}] return "datetime('$my_val')" } } } } ::itcl::class Postgresql { inherit RootFormatter public method DATE {field_name val convert_to} { set secs [clock scan $val] set my_val [clock format $secs -format {%Y-%m-%d}] return "'$my_val'" } public method DATETIME {field_name val convert_to} { set secs [clock scan $val] set my_val [clock format $secs -format {%Y-%m-%d %T}] return "'$my_val'" } public method NOW {field_name val convert_to} { switch $convert_to { # we try to be coherent with the original purpose of this method whose # goal is to provide a uniform way to handle timestamps. # E.g.: Package session expects this case to return a timestamp in seconds # so that differences with timestamps returned by [clock seconds] # can be done and session expirations are computed consistently. # (Bug #53703) SECS { if {[::string compare $val "now"] == 0} { # set secs [clock seconds] # set my_val [clock format $secs -format {%Y%m%d%H%M%S}] # return $my_val return [clock seconds] } else { return "extract(epoch from $field_name)" } } default { if {[::string compare $val, "now"] == 0} { set secs [clock seconds] } else { set secs [clock scan $val] } # this is kind of going back and forth from the same # format, return "'[clock format $secs -format {%Y-%m-%d %T}]'" } } } } ::itcl::class Oracle { inherit RootFormatter public method DATE {field_name val convert_to} { set secs [clock scan $val] set my_val [clock format $secs -format {%Y-%m-%d}] return "to_date('$my_val','YYYY-MM-DD')" } public method DATETIME {field_name val convert_to} { set secs [clock scan $val] set my_val [clock format $secs -format {%Y-%m-%d %T}] return "to_date('$my_val','YYYY-MM-DD HH24:MI:SS')" } public method NOW {field_name val convert_to} { switch $convert_to { SECS { if {[::string compare $val "now"] == 0} { set secs [clock seconds] set my_val [clock format $secs -format {%Y%m%d%H%M%S}] return $my_val } else { return "($field_name - to_date('1970-01-01')) * 86400" #return "to_char($field_name, 'YYYYMMDDHH24MISS')" } } default { if {[::string compare $val "now"] == 0} { set secs [clock seconds] } else { set secs [clock scan $val] } set my_val [clock format $secs -format {%Y-%m-%d %T}] return "to_date('$my_val', 'YYYY-MM-DD HH24:MI:SS')" } } } } }; ## namespace eval DIO package provide dio::formatters 1.0