#------------------------------------------------------------------------------
#$Author: andrius $
#$Date: 2020-12-22 04:44:09 -0500 (Tue, 22 Dec 2020) $ 
#$Revision: 7142 $
#$URL: svn://saulius-grazulis.lt/restful/tags/v0.15.2/lib/RestfulDB/SQL.pm $
#------------------------------------------------------------------------------
#*
#  An object to encapsulate a database connection and meta-table
#  queries for the Restful DB interface.
#**

package RestfulDB::SQL;
use warnings;
use strict;

require Exporter;
our @ISA = qw( Exporter );
our @EXPORT_OK = qw(
    is_blob
    is_character_string
    is_integer
    is_internal_SQLite_table
    is_numerical
    is_text
);

use Data::UUID;
use Digest::MD5 qw(md5_hex);
use DBI;
use File::Basename qw(basename);
use List::MoreUtils qw(uniq);
use List::Util qw(any);
use LWP::Simple;
use Object::Lazy;
use POSIX qw(strftime);
use Digest::SHA qw(sha256_hex);

use Database::Filter;
use Database::ForeignKey;
use Database::Order;
use Database::View;
use RestfulDB::Defaults;
use RestfulDB::Exception;
use RestfulDB::Schema::DBI;

our $filter_operators = {
    eq => '=',
    ne => '!=',
    gt => '>',
    lt => '<',
    le => '<=',
    ge => '>=',
    contains => 'contains',
    starts => 'starts with',
    ends => 'ends with',
    known => 'is known',
    unknown => 'is unknown',
    ## Removing 'like' and 'unlike' for now as they are not supported
    ## by OPTiMaDe specification
    # like => 'like',
    # unlike => 'unlike',
};

## @function is_blob ($sql_type_name)
# Determines whether the specified type is a blob.
# @retval bool
sub is_blob
{
    my ($type_name) = @_;
    my $lc_type_name = lc($type_name);

    return $lc_type_name eq 'blob' ||
        $lc_type_name eq 'tinyblob' ||
        $lc_type_name eq 'mediumblob' ||
        $lc_type_name eq 'longblob';
}

## @function is_text ($sql_type_name)
# Determines whether the specified type is a text.
# @retval bool
sub is_text
{
    my ($type_name) = @_;

    return $type_name =~ /^(tiny|medium|long)?text$/i;
}

## @function is_character_string ($sql_type_name)
# Determines whether the specified type is character string.
sub is_character_string
{
    my ($type_name) = @_;

    for (lc $type_name) {
        # Removing the length qualifier, if any
        s/\(\d+[kmgt]?\)$//;

        # Character string according to ANSI SQL specification, as given in
        # https://en.wikipedia.org/w/index.php?title=SQL_syntax&oldid=800924432
        return 1 if /^((national )?char(acter( varying)?)?|varchar)$/;
        return 1 if /^n?(var)?char$/;
        return 1 if /^n?clob$/;
        return 1 if /^(national )?character large object$/;

        # TEXT is implemented in MySQL and SQLite
        return 1 if is_text( $_ );
    }
}

## @function is_integer ($sql_type_name)
# Determines whether the specified type is integer.
sub is_integer
{
    my ($type_name) = @_;

    for (lc $type_name) {
        # Numerical according to ANSI SQL specification, as given in
        # https://en.wikipedia.org/w/index.php?title=SQL_syntax&oldid=800924432
        return 1 if /^(small|medium|big)?int$/;
        return 1 if /^integer$/;

        # From https://www.sqlite.org/datatype3.html, except DATE and
        # DATETIME, which possibly belong to temporal (datetime)
        return 1 if /^(tiny|unsigned big )?int$/;
        return 1 if /^int[28]$/;
    }
}

## @function is_numerical ($sql_type_name)
# Determines whether the specified type is numerical.
sub is_numerical
{
    my ($type_name) = @_;

    for (lc $type_name) {
        return 1 if is_integer( $type_name );

        # Numerical or Boolean according to ANSI SQL specification, as given in
        # https://en.wikipedia.org/w/index.php?title=SQL_syntax&oldid=800924432
        return 1 if /^(boolean|float|real|double precision)$/;
        return 1 if /^(numeric|decimal|decfloat)\(/;

        # From https://www.sqlite.org/datatype3.html, except DATE and
        # DATETIME, which possibly belong to temporal (datetime)
        return 1 if /^(double|numeric)$/;
    }
}

## @function is_internal_SQLite_table ($table)
# Indicates whether table name matches name of internal SQLite table
# name.
sub is_internal_SQLite_table
{
    my( $table ) = @_;
    return 1 if $table =~ /^sqlite_((temp_)?master|sequence)$/;
    return 0;
}

1;
