blob: 91f59de3a0eef868ff74d031c0ea66c403e0b112 [file] [log] [blame]
#!/usr/bin/perl
#
# 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.
#
# $Header: //gpsql/feature/hd2.0/private-lili/src/include/catalog/calico.pl#2 $
#
# SLZY_HDR_END
use POSIX;
use Pod::Usage;
use Getopt::Long;
use Data::Dumper;
#use JSON;
use strict;
use warnings;
# SLZY_POD_HDR_BEGIN
# WARNING: DO NOT MODIFY THE FOLLOWING POD DOCUMENT:
# Generated by sleazy.pl version 9 (release Mon Feb 4 13:03:01 2013)
# Make any changes under SLZY_TOP_BEGIN/SLZY_LONG_BEGIN
=head1 NAME
B<calico.pl> - CaQL catalog query code generation
=head1 VERSION
This document describes version 55 of calico.pl, released
Mon Feb 4 13:07:23 2013.
=head1 SYNOPSIS
B<calico.pl>
Options:
-help brief help message
-man full documentation
-interactive Run calico.pl as an interactive shell
-metadata json document describing the catalog
-gperf Construct an input file for gperf
-dump dump the data structures
-logquery turn on logging (elog) for all basic functions
-logquery_hash turn on filter logging
-lockcheck check locking
-readlock check locks for read
-holdtablelock hold table locks until transaction commit
-lockblacklist tables excluded from locking
-lockwhitelist tables covered by locking
-filemap build a json file mapping files to catalog tables
-basedef build a json file of basic caql definitions
-uniqdef build a json file of uniq caql definitions
-inputfiles list of cql input files
=head1 OPTIONS
=over 8
=item B<-help>
Print a brief help message and exits.
=item B<-man>
Prints the manual page and exits.
=item B<-interactive>
Runs calico.pl as an interactive shell
=item B<-metadata> <filename> (Required)
the metadata file is a json document describing the catalog generated by tidycat
=item B<-gperf> <filename>
Construct an input file for gperf
=item B<-dump>
dump the data structures
=item B<-logquery>
Turn on logging (elog) for all basic functions. Use caqltrack.sql to
process these statements from the logs to calculate code coverage.
=item B<-logquery_hash>
turn on filter logging for all basic functions (filter duplicate callers)
=item B<-lockcheck>
acquire locks to prevent write skew
=item B<-readlock>
acquire primary key locks in basic fn prior to read
=item B<-holdtablelock>
Normally, table locks are released at heap_close at caql_endscan.
When this flag is set, the table lock is retained until transaction
commit/abort.
=item B<-lockblacklist>
tables excluded from locking
=item B<-lockwhitelist>
tables covered by locking
=item B<-filemap> <filename>
build a json file mapping source files to catalog tables referenced in cql statements (for pablopcatso)
=item B<-basedef> <filename>
build a json file of basic caql definitions
=item B<-uniqdef> <filename>
build a json file of uniq caql definitions
=item B<-inputfiles> <filename>
file containing list of cql input files (1 per line). If defined, ignores the filenames from the argument list and only processes files listed in the input file.
=back
=head1 DESCRIPTION
calico.pl discovers cql() functions with CaQL statements and generates
the corresponding C functions in catquery.c. It acts as a compiler,
parsing CaQL, a small, SQL-like query language, and it generates the
corresponding low-level heap/index lookup functions, or CaQL "basic
queries". At runtime, the cql functions use a hash of the CaQL
statement to dispatch to the associated "basic query" function.
The CaQL parsing and code generation is driven by the tidycat json
metadata file, which describes the catalog tables, their indexes and
dependencies.
=head2 INSERT/UPDATE Syntax
CaQL has been extended to handle INSERT and UPDATE.
=head2 Locking
The readlock/lockcheck options use the tidycat dependency information
(JSON) to generate the insert/update/delete ("iud") locking functions.
Without locking, concurrent transactions can cause catalog corruption,
e.g the case where one transaction does CREATE TABLE in a schema and a
concurrent transaction DROPs the schema. The caql_lockwell() function
locks the "primary key" index(es) and any associated foreign key
indexes, ensuring that referential integrity is maintained.
=head1 CAVEATS/FUTURE WORK
=head2 External Catalog
After the entire catalog is converted to CaQL, we should be able to
restructure the system to support a single, external catalog. At this
stage we may switch from calico code generation to a direct
implementation of catquery as an external catalog API.
=head2 Catalog Bloat
Need to restructure the catalog to eliminate bloat.
=head1 AUTHORS
Apache HAWQ
Address bug reports and comments to: dev@hawq.apache.org
=cut
# SLZY_POD_HDR_END
# SLZY_GLOB_BEGIN
my $glob_id = "";
my $glob_glob;
# SLZY_GLOB_END
sub glob_validate
{
# map files to catalog tables
$glob_glob->{fil2tab} = {};
# look for JSON on normal path, but if it isn't there, add the
# directory containing this script and look for it there...
unless (eval "require JSON")
{
use FindBin qw($Bin);
use lib "$Bin";
unless (eval "require JSON")
{
die("Fatal Error: The required package JSON is not installed -- please download it from www.cpan.org\n");
exit(1);
}
}
# readlock implies general lockcheck
$glob_glob->{lockcheck} = 1
if (exists($glob_glob->{readlock})
&& ($glob_glob->{readlock}));
# DO NOT extend this list! These tables are *exempt* from the
# autogenerated primary locking mechanism for historical reasons.
#
my @pklock_exception_tables =
qw(
pg_class
pg_authid
pg_largeobject
pg_statistic
pg_stat_last_operation
pg_stat_last_shoperation
gp_distribution_policy
pg_depend
pg_shdepend
pg_description
pg_shdescription
);
# build locking whitelist and blacklist
my $blacklist = {};
for my $tname (@pklock_exception_tables)
{
$blacklist->{$tname} = 1;
}
if (exists($glob_glob->{lockblacklist})
&& defined($glob_glob->{lockblacklist}))
{
my @foo = split(/,/, $glob_glob->{lockblacklist});
for my $tname (@foo)
{
$blacklist->{$tname} = 1;
}
}
$glob_glob->{lock_exceptions} = {blacklist => $blacklist};
if (exists($glob_glob->{lockwhitelist})
&& defined($glob_glob->{lockwhitelist}))
{
my @foo = split(/,/, $glob_glob->{lockwhitelist});
my $whitelist = {};
for my $tname (@foo)
{
die "table $tname cannot be on both " .
"lockwhitelist and lockblacklist"
if (exists($blacklist->{$tname}));
$whitelist->{$tname} = 1;
}
$glob_glob->{lock_exceptions}->{whitelist} = $whitelist
if (scalar(@foo));
}
# print Data::Dumper->Dump([\$glob_glob]);
}
# SLZY_CMDLINE_BEGIN
# WARNING: DO NOT MODIFY THE FOLLOWING SECTION:
# Generated by sleazy.pl version 9 (release Mon Feb 4 13:03:01 2013)
# Make any changes under SLZY_TOP_BEGIN/SLZY_LONG_BEGIN
# Any additional validation logic belongs in glob_validate()
BEGIN {
my $s_help = 0; # brief help message
my $s_man = 0; # full documentation
my $s_interactive = 0; # Run calico.pl as an interactive shell
my $s_metadata; # json document describing the catalog
my $s_gperf; # Construct an input file for gperf
my $s_dump = 0; # dump the data structures
my $s_logquery = 0; # turn on logging (elog) for all basic functions
my $s_logquery_hash = 0; # turn on filter logging
my $s_lockcheck = 0; # check locking
my $s_readlock = 0; # check locks for read
my $s_holdtablelock = 0; # hold table locks until transaction commit
my $s_lockblacklist; # tables excluded from locking
my $s_lockwhitelist; # tables covered by locking
my $s_filemap; # build a json file mapping files to catalog tables
my $s_basedef; # build a json file of basic caql definitions
my $s_uniqdef; # build a json file of uniq caql definitions
my $s_inputfiles; # list of cql input files
my $slzy_argv_str;
$slzy_argv_str = quotemeta(join(" ", @ARGV))
if (scalar(@ARGV));
GetOptions(
'help|?' => \$s_help,
'man' => \$s_man,
'interactive' => \$s_interactive,
'metadata|json=s' => \$s_metadata,
'gperf|perf:s' => \$s_gperf,
'dump' => \$s_dump,
'logquery|elog' => \$s_logquery,
'logquery_hash' => \$s_logquery_hash,
'lockcheck' => \$s_lockcheck,
'readlock' => \$s_readlock,
'holdtablelock|holdlock' => \$s_holdtablelock,
'lockblacklist|lbl:s' => \$s_lockblacklist,
'lockwhitelist|lwl:s' => \$s_lockwhitelist,
'filemap:s' => \$s_filemap,
'basedef:s' => \$s_basedef,
'uniqdef:s' => \$s_uniqdef,
'inputfiles|infiles:s' => \$s_inputfiles,
)
or pod2usage(2);
pod2usage(-msg => $glob_id, -exitstatus => 1) if $s_help;
pod2usage(-msg => $glob_id, -exitstatus => 0, -verbose => 2) if $s_man;
$glob_glob = {};
# version and properties from json definition
$glob_glob->{_sleazy_properties} = {};
$glob_glob->{_sleazy_properties}->{version} = '55';
$glob_glob->{_sleazy_properties}->{AUTHORNAME} = 'Jeffrey I Cohen';
$glob_glob->{_sleazy_properties}->{BUGEMAIL} = 'Address bug reports and comments to: jcohen@greenplum.com';
$glob_glob->{_sleazy_properties}->{COPYDATES} = '2011, 2012, 2013';
$glob_glob->{_sleazy_properties}->{COPYHOLDER} = 'Greenplum';
$glob_glob->{_sleazy_properties}->{slzy_date} = '1360012043';
$glob_glob->{_sleazy_properties}->{slzy_argv_str} = $slzy_argv_str;
die ("missing required argument for 'metadata'")
unless (defined($s_metadata));
if (defined($s_metadata))
{
die ("invalid argument for 'metadata': file $s_metadata does not exist")
unless (-e $s_metadata);
}
if (defined($s_inputfiles))
{
die ("invalid argument for 'inputfiles': file $s_inputfiles does not exist")
unless (-e $s_inputfiles);
}
$glob_glob->{interactive} = $s_interactive if (defined($s_interactive));
$glob_glob->{metadata} = $s_metadata if (defined($s_metadata));
$glob_glob->{gperf} = $s_gperf if (defined($s_gperf));
$glob_glob->{dump} = $s_dump if (defined($s_dump));
$glob_glob->{logquery} = $s_logquery if (defined($s_logquery));
$glob_glob->{logquery_hash} = $s_logquery_hash if (defined($s_logquery_hash));
$glob_glob->{lockcheck} = $s_lockcheck if (defined($s_lockcheck));
$glob_glob->{readlock} = $s_readlock if (defined($s_readlock));
$glob_glob->{holdtablelock} = $s_holdtablelock if (defined($s_holdtablelock));
$glob_glob->{lockblacklist} = $s_lockblacklist if (defined($s_lockblacklist));
$glob_glob->{lockwhitelist} = $s_lockwhitelist if (defined($s_lockwhitelist));
$glob_glob->{filemap} = $s_filemap if (defined($s_filemap));
$glob_glob->{basedef} = $s_basedef if (defined($s_basedef));
$glob_glob->{uniqdef} = $s_uniqdef if (defined($s_uniqdef));
$glob_glob->{inputfiles} = $s_inputfiles if (defined($s_inputfiles));
glob_validate();
}
# SLZY_CMDLINE_END
# SLZY_TOP_BEGIN
if (0)
{
my $bigstr = <<'EOF_bigstr';
{
"args" : [
{
"alias" : "?",
"long" : "Print a brief help message and exits.",
"name" : "help",
"required" : "0",
"short" : "brief help message",
"type" : "untyped"
},
{
"long" : "Prints the manual page and exits.",
"name" : "man",
"required" : "0",
"short" : "full documentation",
"type" : "untyped"
},
{
"long" : "Runs calico.pl as an interactive shell",
"name" : "interactive",
"required" : "0",
"short" : "Run calico.pl as an interactive shell",
"type" : "untyped"
},
{
"alias" : "json",
"long" : "the metadata file is a json document describing the catalog generated by tidycat",
"name" : "metadata",
"required" : "1",
"short" : "json document describing the catalog",
"type" : "file"
},
{
"alias" : "perf",
"long" : "Construct an input file for gperf",
"name" : "gperf",
"required" : "0",
"short" : "Construct an input file for gperf",
"type" : "outfile"
},
{
"long" : "dump the data structures",
"name" : "dump",
"required" : "0",
"short" : "dump the data structures",
"type" : "untyped"
},
{
"alias" : "elog",
"long" : "$loglong",
"name" : "logquery",
"required" : "0",
"short" : "turn on logging (elog) for all basic functions",
"type" : "untyped"
},
{
"long" : "turn on filter logging for all basic functions (filter duplicate callers)",
"name" : "logquery_hash",
"required" : "0",
"short" : "turn on filter logging",
"type" : "untyped"
},
{
"long" : "acquire locks to prevent write skew",
"name" : "lockcheck",
"required" : "0",
"short" : "check locking",
"type" : "untyped"
},
{
"long" : "acquire primary key locks in basic fn prior to read",
"name" : "readlock",
"required" : "0",
"short" : "check locks for read",
"type" : "untyped"
},
{
"alias" : "holdlock",
"long" : "$holdlocklong",
"name" : "holdtablelock",
"required" : "0",
"short" : "hold table locks until transaction commit",
"type" : "untyped"
},
{
"alias" : "lbl",
"name" : "lockblacklist",
"required" : "0",
"short" : "tables excluded from locking",
"type" : "string"
},
{
"alias" : "lwl",
"name" : "lockwhitelist",
"required" : "0",
"short" : "tables covered by locking",
"type" : "string"
},
{
"long" : "build a json file mapping source files to catalog tables referenced in cql statements (for pablopcatso)",
"name" : "filemap",
"required" : "0",
"short" : "build a json file mapping files to catalog tables",
"type" : "outfile"
},
{
"name" : "basedef",
"required" : "0",
"short" : "build a json file of basic caql definitions",
"type" : "outfile"
},
{
"name" : "uniqdef",
"required" : "0",
"short" : "build a json file of uniq caql definitions",
"type" : "outfile"
},
{
"alias" : "infiles",
"long" : "file containing list of cql input files (1 per line). If defined, ignores the filenames from the argument list and only processes files listed in the input file.",
"name" : "inputfiles",
"required" : "0",
"short" : "list of cql input files",
"type" : "file"
}
],
"long" : "$toplong",
"properties" : {
"AUTHORNAME" : "Jeffrey I Cohen",
"BUGEMAIL" : "Address bug reports and comments to: jcohen@greenplum.com",
"COPYDATES" : "2011, 2012, 2013",
"COPYHOLDER" : "Greenplum",
"slzy_date" : 1360012043
},
"short" : "CaQL catalog query code generation",
"version" : "55"
}
EOF_bigstr
}
# SLZY_TOP_END
# SLZY_LONG_BEGIN
if (0)
{
my $toplong = <<'EOF_toplong';
calico.pl discovers cql() functions with CaQL statements and generates
the corresponding C functions in catquery.c. It acts as a compiler,
parsing CaQL, a small, SQL-like query language, and it generates the
corresponding low-level heap/index lookup functions, or CaQL "basic
queries". At runtime, the cql functions use a hash of the CaQL
statement to dispatch to the associated "basic query" function.
The CaQL parsing and code generation is driven by the tidycat json
metadata file, which describes the catalog tables, their indexes and
dependencies.
{HEAD2} INSERT/UPDATE Syntax
CaQL has been extended to handle INSERT and UPDATE.
{HEAD2} Locking
The readlock/lockcheck options use the tidycat dependency information
(JSON) to generate the insert/update/delete ("iud") locking functions.
Without locking, concurrent transactions can cause catalog corruption,
e.g the case where one transaction does CREATE TABLE in a schema and a
concurrent transaction DROPs the schema. The caql_lockwell() function
locks the "primary key" index(es) and any associated foreign key
indexes, ensuring that referential integrity is maintained.
{HEAD1} CAVEATS/FUTURE WORK
{HEAD2} External Catalog
After the entire catalog is converted to CaQL, we should be able to
restructure the system to support a single, external catalog. At this
stage we may switch from calico code generation to a direct
implementation of catquery as an external catalog API.
{HEAD2} Catalog Bloat
Need to restructure the catalog to eliminate bloat.
EOF_toplong
my $loglong = <<'EOF_loglong';
Turn on logging (elog) for all basic functions. Use caqltrack.sql to
process these statements from the logs to calculate code coverage.
EOF_loglong
my $holdlocklong = <<'EOF_holdlocklong';
Normally, table locks are released at heap_close at caql_endscan.
When this flag is set, the table lock is retained until transaction
commit/abort.
EOF_holdlocklong
}
# SLZY_LONG_END
sub doformat
{
my ($bigstr, $kv) = @_;
my %blankprefix;
# find format expressions with leading blanks
if ($bigstr =~ m/\n/)
{
my @foo = split(/\n/, $bigstr);
for my $lin (@foo)
{
next unless ($lin =~ m/^\s+\{.*\}/);
# find the first format expression after the blank prefix
my @baz = split(/\}/, $lin, 2);
my $firstf = shift @baz;
my @zzz = ($firstf =~ m/^(\s+)\{(.*)$/);
next unless (defined($zzz[1]) &&
length($zzz[1]));
my $k2 = quotemeta($zzz[1]);
if (exists($blankprefix{$k2}))
{
# print STDERR Data::Dumper->Dump(\@foo);
# print STDERR Data::Dumper->Dump([\%blankprefix]);
die "duplicate use of prefixed pattern $k2 for\n$bigstr"
unless ($blankprefix{$k2} eq $zzz[0]);
}
# store the prefix
$blankprefix{$k2} = $zzz[0];
}
}
# print Data::Dumper->Dump([%blankprefix]);
while (my ($kk, $vv) = each(%{$kv}))
{
my $subi = '{' . quotemeta($kk) . '}';
my $v2 = $vv;
if (exists($blankprefix{quotemeta($kk)}) &&
($v2 =~ m/\n/))
{
my @foo = split(/\n/, $v2);
# for a multiline substitution, prefix every line with the
# offset of the original token
$v2 = join("\n" . $blankprefix{quotemeta($kk)}, @foo);
# fixup trailing newline if necessary
if ($vv =~ m/\n$/)
{
$v2 .= "\n"
unless ($v2 =~ m/\n$/);
}
}
$bigstr =~ s/$subi/$v2/gm;
}
return $bigstr;
}
# only allow alphanums, and quote all other chars as hex string
sub sql_func_quurl
{
my $str = shift;
$str =~ s/([^a-zA-Z0-9])/uc(sprintf("%%%02lx", ord $1))/eg;
return $str;
}
# more "relaxed" version of quurl function -- allow basic punctuation
# with the exception of "%" and quote characters
sub sql_func_quurl2
{
my $str = shift;
my $pat1 = '[^a-zA-Z0-9' .
quotemeta(' ~!@#$^&*()-_=+{}|[]:;<>,.?/') . ']';
$str =~ s/($pat1)/uc(sprintf("%%%02lx", ord $1))/eg;
return $str;
}
# unconvert quoted strings
sub sql_func_unquurl
{
my $str = shift;
$str =~ s/\%([A-Fa-f0-9]{2})/pack('C', hex($1))/seg;
return $str;
}
sub scanhdr
{
my $bigstr = <<'EOF_bigstr';
SysScanDesc {SCAN};
{RELATIONDEF}
{HEAPTUPLEDEF}
EOF_bigstr
return $bigstr;
} # end scanhdr
sub scankeyinit
{
my $bigstr = <<'EOF_bigstr';
ScanKeyInit(&{KEY}[{KEYOFFSET}],
{ANUMKEYCOL},
BTEqualStrategyNumber, {FMGREQOID},
{KVTRANSFORM}({KEYVAL}));
EOF_bigstr
return $bigstr;
} # end scankeyinit
sub readpkeyhashinit
{
my $bigstr = <<'EOF_bigstr';
newhash = caql_pkhash(pCtx, newhash, ({KEYVAL}),
false /* isnull */, {MHTYPOID});
EOF_bigstr
return $bigstr;
} # end readkpeyhashinit
sub idxokfunc
{
my $bigstr = <<'EOF_bigstr';
/* always use the index (if possible) unless the caller states otherwise */
if (!pCtx->cq_setidxOK)
pCtx->cq_useidxOK = true;
EOF_bigstr
return $bigstr;
}
sub nosyscacheid
{
my $bigstr = <<'EOF_bigstr';
Assert (!pCtx->cq_usesyscache);
pCtx->cq_usesyscache = false; /* complain in debug, work in production */
EOF_bigstr
return $bigstr;
}
sub syscacheid
{
my $bigstr = <<'EOF_bigstr';
/* always use the syscache unless the caller states otherwise */
if (!pCtx->cq_setsyscache)
{
pCtx->cq_usesyscache = true;
/* Normally, must match all columns of the index to use syscache,
* except for case of SearchSysCacheList
*/
if (!pCtx->cq_bCacheList && (pCtx->cq_NumKeys != {NUMSYSCACHEIDXCOLS}))
pCtx->cq_usesyscache = false;
/* ARD-28, MPP-16119: can only use SnapshowNow with syscache */
if (pCtx->cq_snapshot != SnapshotNow)
{
/* Complain in debug, but behave in production */
Assert(!pCtx->cq_usesyscache);
pCtx->cq_setsyscache = true;
pCtx->cq_usesyscache = false;
}
}
if (pCtx->cq_usesyscache)
{
pCtx->cq_cacheId = {SYSCACHEIDENTIFIER};
/* number of keys must match (unless a SearchSysCacheList ) */
Assert(pCtx->cq_bCacheList || (pCtx->cq_NumKeys == {NUMSYSCACHEIDXCOLS}));
}
/* else perform heap/index scan */
EOF_bigstr
return $bigstr;
} # end syscacheid
sub syscachetupdesc
{
my $bigstr = <<'EOF_bigstr';
/* XXX XXX : NOTE: disable heap_open/lock for syscache
only if : no external lock mode or external relation
*/
if ((!pCtx->cq_setlockmode) &&
pCtx->cq_usesyscache &&
(AccessShareLock == pCtx->cq_lockmode))
{
pCtx->cq_externrel = true; /* pretend we have external relation */
pCtx->cq_heap_rel = InvalidRelation;
/*
pCtx->cq_tupdesc = SysCache[pCtx->cq_cacheId]->cc_tupdesc;
*/
return NULL; /* XXX XXX: return early - don't open heap */
}
else
EOF_bigstr
return $bigstr;
} # end syscachetupdesc
## ARD-27: check lock strength for query/update in caql basic functions
sub rel_lock_test
{
my $bigstr = <<'EOF_bigstr';
/* test lock strength */
if ((!pCtx->cq_setlockmode) &&
RelationIsValid(pCtx->cq_heap_rel))
{
LOCKMODE mode = pCtx->cq_lockmode;
LOCKMASK gm =
LockRelationOid_GetGrantMask(
RelationGetRelid(pCtx->cq_heap_rel),
"{FUNCNAME}");
for (; mode < MAX_LOCKMODES; mode++)
{
if (gm & LOCKBIT_ON(mode))
break;
}
Assert(gm & LOCKBIT_ON(mode));
}
EOF_bigstr
return $bigstr;
} # end rel_lock_test
sub beginscan
{
my $bigstr = <<'EOF_bigstr';
systable_beginscan({REL},
{INDEX},
{INDEXOK},
{SNAPSHOT}, {KEYNUM}, {KEY});
EOF_bigstr
return $bigstr;
} # end beginscan
sub nextscan
{
my $bigstr = "systable_getnext({SCAN})";
return $bigstr;
} # end nextscan
sub endscan
{
my $bigstr = "systable_endscan({SCAN})";
return $bigstr;
} # end endscan
sub scanfuncbody
{
my $bigstr = <<'EOF_bigstr';
void
{FUNCNAME}({FUNCARGS})
{
{SCANHDR}
{OPENREL}
{SCANKEYINIT}
{SCAN} = {BEGINSCAN}
{TUPLELOOPGUTS}
/* Clean up after the scan */
{ENDSCAN}
{CLOSEREL}
}
EOF_bigstr
return $bigstr;
} # end scanfuncbody
# XXX XXX:
# NOTE: logging function requires cql filename, lineno for debug
#
sub selectfrom_elog_hash
{
my $bigstr = <<'EOF_bigstr';
SUPPRESS_ERRCONTEXT_DECLARE;
SUPPRESS_ERRCONTEXT_PUSH();
if (gp_enable_caql_logging)
{
CaQLLogTag caqllogtag;
CaQLLogEntry *entry;
int hashcode;
int len;
bool found = false;
/* memset caqllogtag is needed, as memcmp is done during hash_search_with_hash_value */
MemSet(&caqllogtag, 0, sizeof(caqllogtag));
len = (strlen(pcql->filename) > MAXPGPATH ? MAXPGPATH : strlen(pcql->filename));
memcpy(caqllogtag.filename, pcql->filename, len);
caqllogtag.lineno = pcql->lineno;
/* compute the hash */
hashcode = caqlLogHashCode(&caqllogtag);
/* look up the hash table to see if this line has been logged before */
entry = (CaQLLogEntry *) hash_search_with_hash_value(CaQLLogHash,
(void *)&caqllogtag,
hashcode,
HASH_ENTER, &found);
if (!found)
elog(LOG, "catquery: %s caller: %s %d %d %d ",
"{FUNCNAME}", pcql->filename, pcql->lineno, pCtx->cq_uniqquery_code,
DatumGetObjectId(pcql->cqlKeys[0])
);
}
SUPPRESS_ERRCONTEXT_POP();
EOF_bigstr
return $bigstr;
}
# NOTE WELL: always pass the first argument (pcql->cqlKeys[0] as an OID,
# even if it isn't one (or doesn't exist!). We will sort it out
# later during log processing
sub selectfrom_elog
{
my $bigstr = <<'EOF_bigstr';
caql_logquery("{FUNCNAME}", pcql->filename, pcql->lineno, pCtx->cq_uniqquery_code,
DatumGetObjectId(pcql->cqlKeys[0]));
EOF_bigstr
return $bigstr;
}
# lock entire table if cannot use primary keys
# (used for both read and insert case)
sub readinsert_entiretable_exclusive
{
my $bigstr = <<'EOF_bigstr';
/* cannot get primary key lock -- {READPK_FAILREASON} */
if (pCtx->cq_setpklock)
{
LOCKMODE pklockmode = AccessExclusiveLock;
bool dontWait = false;
if (!pCtx->cq_pklock_excl)
pklockmode = AccessShareLock;
caql_lock_entiretable(pCtx, {RELATIONID},
pklockmode, dontWait);
}
EOF_bigstr
return $bigstr;
}
# share lock entire table (if necessary) if *can* use primary keys
# (used for both read and insert case)
sub readinsert_entiretable_share
{
my $bigstr = <<'EOF_bigstr';
/*
if any caql statement on {RELATIONID} cannot use a
primary key, then need to obtain a share lock
(to block the table exclusive lock)
*/
if (bLockEntireTable)
caql_lock_entiretable(pCtx, {RELATIONID},
AccessShareLock, false);
EOF_bigstr
return $bigstr;
}
sub get_readpk
{
my $bigstr = <<'EOF_bigstr';
/* can get primary key lock for read: {LW_TNAME}({COLSTR}) */
if (pCtx->cq_setpklock)
{
LOCKMODE pklockmode = AccessExclusiveLock;
bool dontWait = false;
Oid newhash = 0;
if (!pCtx->cq_pklock_excl)
pklockmode = AccessShareLock;
{READPK_ENTIRETABLE}
{READPK_INIT}
caql_lockwell(pCtx, {RELATIONID},
pklockmode, NULL,
"{LW_TNAME}",
"{COLSTR}",
{INDEX},
newhash,
dontWait,
true /* ignore invalid tuple */
);
/* XXX XXX: disable syscache because it might need to be invalidated!! */
// pCtx->cq_setsyscache = true;
// pCtx->cq_usesyscache = false;
AcceptInvalidationMessages(); /* syscache could be out of date after lock wait */
}
EOF_bigstr
return $bigstr;
}
sub selectfrom
{
my $selguts = <<'EOF_bigstr';
static
SysScanDesc
{FUNCNAME}({FUNCARGS})
{
{SCANHDR}
Relation rel;
{SELECTFROM_ELOG}
{GET_READPK}
{SYSCACHECHECK}
pCtx->cq_relationId = {RELATIONID};
if (!pCtx->cq_externrel)
{
{SYSCACHETUPDESC}
{
pCtx->cq_heap_rel = heap_open(pCtx->cq_relationId,
pCtx->cq_lockmode);
pCtx->cq_tupdesc = RelationGetDescr(pCtx->cq_heap_rel);
}
}
else
{
/* make sure the supplied relation matches the caql */
if (RelationIsValid(pCtx->cq_heap_rel))
{
Assert({RELATIONID} ==
RelationGetRelid(pCtx->cq_heap_rel));
pCtx->cq_tupdesc = RelationGetDescr(pCtx->cq_heap_rel);
}
{RELLOCKTEST}
}
rel = pCtx->cq_heap_rel;
if (pCtx->cq_usesyscache) return NULL; /* XXX XXX: don't init scan */
{SCANKEYINIT}
{SCAN} = {BEGINSCAN}
return ({SCAN});
}
EOF_bigstr
return $selguts;
} # end selectfrom
sub deletefrom
{
my $loopguts = <<'EOF_bigstr';
/* Delete all the matching tuples */
while (HeapTupleIsValid({TUPLE} = {NEXTSCAN}))
{
if (HeapTupleIsValid({TUPLE}))
simple_heap_delete({REL}, &{TUPLE}->t_self);
}
EOF_bigstr
return doformat(scanfuncbody(),
{
TUPLELOOPGUTS => $loopguts
});
} # end deletefrom
sub duplicate_obj
{
my $loopguts = <<'EOF_bigstr';
{TUPLE} = {NEXTSCAN};
if (HeapTupleIsValid({TUPLE}))
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg({DUPOBJEXISTSMSG})
errOmitLocation(true)));
EOF_bigstr
return doformat(scanfuncbody(),
{
TUPLELOOPGUTS => $loopguts
});
} # end duplicate_obj
sub undef_obj
{
my $loopguts = <<'EOF_bigstr';
{TUPLE} = {NEXTSCAN};
if (!HeapTupleIsValid({TUPLE}))
ereport(ERROR,
(errcode(ERRCODE_UNDEFINED_OBJECT),
errmsg({UNDEFOBJNOTEXISTMSG})
errOmitLocation(true)));
EOF_bigstr
return doformat(scanfuncbody(),
{
TUPLELOOPGUTS => $loopguts
});
} # end undef_obj
# construct a basic function for an INSERT
#
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
#
# Note that if locking is enabled (as a calico option), and
# bLockEntireTable is set, then always lock the table Share (really
# Intent Exclusive) if the table has a primary key, or Exclusive if
# the table does *not* have a primary key. Unlike basic functions
# which fetch tuples (SELECT/DELETE), the INSERT *always* gets a lock
# with the IUD functions, so the basic function for INSERT does *not*
# use cq_setpklock to control lock acquistion.
#
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
sub insertinto
{
my $insertguts = <<'EOF_bigstr';
static
SysScanDesc
{FUNCNAME}({FUNCARGS})
{
Relation rel;
{SELECTFROM_ELOG}
pCtx->cq_relationId = {RELATIONID};
{INSERTPK_ENTIRETABLE}
if (!pCtx->cq_externrel)
{
{SYSCACHETUPDESC}
{
pCtx->cq_heap_rel = heap_open(pCtx->cq_relationId,
pCtx->cq_lockmode);
pCtx->cq_tupdesc = RelationGetDescr(pCtx->cq_heap_rel);
}
}
else
{
/* make sure the supplied relation matches the caql */
if (RelationIsValid(pCtx->cq_heap_rel))
{
Assert({RELATIONID} ==
RelationGetRelid(pCtx->cq_heap_rel));
pCtx->cq_tupdesc = RelationGetDescr(pCtx->cq_heap_rel);
}
{RELLOCKTEST}
}
rel = pCtx->cq_heap_rel;
return NULL; /* XXX XXX: don't init scan */
}
EOF_bigstr
return $insertguts;
} # end insertinto
sub unique_index_by_cols
{
my ($bigh, $tname, $colstr) = @_;
return undef
unless (exists($bigh->{$tname}->{indexes}));
my @ikeylist;
my $idxl = $colstr;
$idxl =~ s/^\s+//;
$idxl =~ s/\s+$//;
if ($idxl =~ m/\,/)
{
@ikeylist = split(/\s*\,\s*/, $idxl)
}
else
{
# single key
push @ikeylist, $idxl;
}
# print "$tname, $colstr\n";
my $cola;
for my $idx (@{$bigh->{$tname}->{indexes}})
{
# print Data::Dumper->Dump(\@ikeylist);
# print Data::Dumper->Dump([$idx]);
next
unless (exists($idx->{unique}) &&
$idx->{unique});
next unless (scalar(@{$idx->{cols}}) == scalar(@ikeylist));
# print Data::Dumper->Dump([$idx]);
my $bMatch;
$cola = [];
$bMatch = 1;
# match keys
for my $ii (0..(scalar(@ikeylist)-1))
{
# list of [colname, col_ops]
#
# so match colname in position 0
my $col = $idx->{cols}->[$ii];
unless ($col->[0] eq $ikeylist[$ii])
{
$bMatch = 0;
last;
}
push @{$cola}, { name => $col->[0], ops => $col->[1] };
}
return [$idx->{CamelCaseIndexId}, $cola]
if ($bMatch);
}
return undef;
}
# choose an index which can satisfy the predicate.
# If bexact=true, then index cols must match predicate col order
# (for ORDER BY)
sub choose_index
{
my ($bigh, $tname, $wpred, $bexact) = @_;
return undef
unless (exists($bigh->{$tname}->{indexes}));
my $numkeys = scalar(@{$wpred});
my %pkeyh; # hash of key col names
for my $pred (@{$wpred})
{
$pkeyh{$pred->{key}} = 1;
}
# for all indexes with matching key columns, build a hash of lists
# of matching indexes (hash by total keys in index). We want the
# "best fit", ie the index has the same number of key columns as
# the predicate.
my %indbynumkey;
for my $inds (@{$bigh->{$tname}->{indexes}})
{
my $matchcol;
my $icolnum;
$matchcol = 0;
$icolnum = scalar(@{$inds->{cols}});
# don't check indexes with insufficient key columns
next
if ($icolnum < $numkeys);
# for my $icol (@{$inds->{cols}})
for my $jj (0..(scalar(@{$inds->{cols}})-1))
{
my $icol = $inds->{cols}->[$jj];
# index prefix must match keys. If it does, increment the
# match count. If all keys are matched then this index is
# a candidate, even if it has extra trailing columns.
if ($bexact)
{
# for an *exact* match, key col order must match predicate
last unless ($icol->[0] eq $wpred->[$jj]->{key});
}
last unless (exists($pkeyh{$icol->[0]}));
$matchcol++;
}
# skip unless found all columns in index
next
unless ($matchcol == $numkeys);
$indbynumkey{$icolnum} = []
unless (exists($indbynumkey{$icolnum}));
my $iih =
{CamelCaseIndexId => $inds->{CamelCaseIndexId}};
$iih->{unique} = 1
if (exists($inds->{unique}) && $inds->{unique});
# if this table is a "dependent class" of the parent table,
# then we can lock the primary key of the parent if we aren't
# using all the columns of our index.
$iih->{primarykey_prefix} = $inds->{primarykey_prefix}
if (exists($inds->{primarykey_prefix}));
# predicate matches all columns of index
# (necessary for syscache or pkey locking)
$iih->{allcols} = 1
if ($icolnum == $numkeys);
$iih->{numcols} = $icolnum;
# can use the syscache iff match all cols of index
if (exists($inds->{with}->{syscacheid}) &&
($icolnum == $numkeys))
{
$iih->{SysCacheIdentifier} = $inds->{with}->{syscacheid};
}
push @{$indbynumkey{$icolnum}}, $iih;
}
return undef
unless (scalar(keys(%indbynumkey)));
# walk the set of matching indexes. Best case is exact prefix
# match, ie the index columns are the same as the predicate key
# cols. But we will accept any case with key prefix. First match
# wins. Assume max 10 keys in index.
for my $ii ($numkeys..10)
{
next unless (exists($indbynumkey{$ii}));
# first one is good enough
return $indbynumkey{$ii}->[0];
}
return undef;
} # end choose_index
sub check_oby_index
{
my ($bigh, $tname, $iname, $wpred) = @_;
die "bad oby index: $iname - no indexes on $tname"
unless (exists($bigh->{$tname}->{indexes}));
my $numkeys = scalar(@{$wpred});
my $iih;
for my $inds (@{$bigh->{$tname}->{indexes}})
{
next unless ($iname eq $inds->{CamelCaseIndexId});
my $icolnum = scalar(@{$inds->{cols}});
# NOTE: same logic as choose_index()
$iih =
{CamelCaseIndexId => $inds->{CamelCaseIndexId}};
$iih->{unique} = 1
if (exists($inds->{unique}) && $inds->{unique});
# if this table is a "dependent class" of the parent table,
# then we can lock the primary key of the parent if we aren't
# using all the columns of our index.
$iih->{primarykey_prefix} = $inds->{primarykey_prefix}
if (exists($inds->{primarykey_prefix}));
# predicate matches all columns of index
# (necessary for syscache or pkey locking)
$iih->{allcols} = 1
if ($icolnum == $numkeys);
$iih->{numcols} = $icolnum;
# XXX XXX: for ORDER BY case, we might use the syscache even
# if all the index columns *don't* match, for
# SearchSysCacheList -- use check of "allcols" to determine
# validity
$iih->{SysCacheIdentifier} = $inds->{with}->{syscacheid}
if (exists($inds->{with}->{syscacheid}));
last;
}
die "bad oby index: $iname"
unless (defined($iih));
return $iih;
} # end check_oby_index
# build hash of fixed fields for all tables/structs
sub getfixedfields
{
my $bigh = shift;
# known list of basic variable length types (a bit of overkill,
# since only need to worry about bootstrap)
my @variable =
qw(
anyarray
anytable
bit
bpchar
bytea
cidr
inet
int2vector
numeric
oidvector
path
polygon
record
refcursor
text
varbit
varchar
xml
);
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# NOTE: Add timestamp with timezone because they make me
# nervous -- treat them as variable length to prevent GETSTRUCT
# issues...
# push @variable, "time";
push @variable, "timestamp_with_timezone";
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
my $varitype = join("|", @variable);
my $ffh = {};
while (my ($kk, $vv) = each(%{$bigh}))
{
my $tname = $kk;
next # ignore comments
if ($tname =~ m/^\_\_/);
my $cols = [];
my $ii;
$ii = 0;
my $lastfixed = 0;
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# MPP-17159: hack pg_extprotocol to deal with NULLs in fixed part
if ($tname eq "pg_extprotocol")
{
$lastfixed = 1;
}
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
for my $coldef (@{$vv->{cols}})
{
my $isvari = (($coldef->{sqltype} =~ m/^($varitype)$/) ||
# no arrays
($coldef->{sqltype} =~ m/\[\]$/));
unless ($lastfixed)
{
if ($isvari)
{
$lastfixed = $ii + 1;
}
}
$ii++;
push @{$cols}, {
colname => $coldef->{colname},
attnum => $ii,
ctype => $coldef->{ctype},
sqltype => $coldef->{sqltype},
fixed =>
($isvari || ($lastfixed && ($lastfixed <= $ii))) ? 0 : 1
};
}
$ffh->{$tname} = {
tname => $tname,
relid => $vv->{CamelCaseRelationId},
maxfixedattnum => $lastfixed,
cols => $cols
};
} # end while kk,vv
return $ffh;
} # end getfixedfields
sub colops2typoid
{
my $colops = shift;
$colops =~ s/\_ops//;
$colops = uc($colops);
$colops .= "OID";
$colops = "INT8OID"
if ($colops =~ m/BIGINTOID/);
return $colops;
}
# convert a column name to an "Anum_" column id
sub anum_key
{
my ($tname, $wkey) = @_;
my $atname = $tname;
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX: stupid last_operation/shoperation fixup
if ($tname =~ m/pg\_stat\_last_(sh)?operation/)
{
$atname =~ s/eration$//;
$atname =~ s/stat\_last\_/statlast/;
}
if ($tname =~ m/gp_distribution_policy/)
{
$atname =~ s/distribution\_//;
}
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
return "Anum_" . $atname . "_" . $wkey;
}
sub struct_form_tname
{
my $tname = shift;
my $atname = $tname;
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX: stupid last_operation/shoperation fixup
if ($tname =~ m/pg\_stat\_last_(sh)?operation/)
{
$atname =~ s/eration$//;
$atname =~ s/stat\_last\_/statlast/;
}
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
return "Form_" . $atname;
}
# returns true if table is a locking exception (no locks required),
# else false. If the blacklist exists, it takes precedence over the
# whitelist. All blacklisted tables are excluded from locking. If
# there *isn't* a whitelist, all tables that are *not* on the
# blacklist are implicitly on the whitelist. If there *is* a
# whitelist, all tables that are *not* on the whitelist are implicitly
# on the blacklist.
sub check_locking_exception
{
my $tname = shift;
return 0
unless (exists($glob_glob->{lock_exceptions}));
if (exists($glob_glob->{lock_exceptions}->{blacklist}))
{
return 1
if ($glob_glob->{lock_exceptions}->{blacklist}->{$tname});
}
# if a white list exists, and the table is *not* on it, treat it
# as an exception.
if (exists($glob_glob->{lock_exceptions}->{whitelist}))
{
return 1
unless ($glob_glob->{lock_exceptions}->{whitelist}->{$tname});
}
# if we get here:
# the table wasn't blacklisted.
# if we had a whitelist, the table must be on it (because if
# the table wasn't on it function should have returned TRUE
# already)
return 0;
} # end check_locking_exception
# build C function for caql query expressions
sub dothing
{
my ($bigh, $qry, $funcname, $bqh) = @_;
my $filerr = "";
my $filenames = "";
# build list of filenames for this basic query
if (defined($bqh) && exists($bqh->{files}))
{
$filenames = join("\n\t",
(keys(%{$bqh->{files}})));
}
$filerr = "\nfiles: $filenames"
if (defined($filenames) && length($filenames));
# error msg: basic query + all files that reference it
my $qerr = $qry . $filerr;
$funcname = "foo"
unless (defined($funcname));
my $tname;
my $domethod;
my $where1;
my ($wkey, $wval, $wcmp);
my $bAllEq = 1; # set false if find a non-equal comparison
if ($qry =~ m/^\s*(delete|undef|dup|select)/i)
{
my @foo =
($qry =~
m/(delete|undef|dup|select\s+\*)\s+from\s+(\w*)(?:\s+where)?/);
die "bad tname for $qerr" unless (2 == scalar(@foo));
$domethod = shift @foo;
$domethod = "select"
if ($domethod =~ m/^select/);
$tname = shift @foo;
}
elsif ($qry =~ m/^\s*insert\s+into/i)
{
my @foo =
($qry =~
m/^\s*insert\s+into\s+(\w*)/i);
die "bad tname for $qerr" unless (1 == scalar(@foo));
$domethod = "insert";
$tname = shift @foo;
}
else
{
die "bad cql: $qerr";
}
# map the files back to the catalog tables (for pablopcatso)
if (defined($bqh) && exists($bqh->{files}))
{
use File::Basename;
for my $k1 (keys(%{$bqh->{files}}))
{
my $fnam = basename($k1);
# XXX XXX: fix analyze
if ($fnam eq "analyze.c")
{
if ($k1 =~ m|commands/analyze|)
{
$fnam = "commands/analyze";
}
elsif ($k1 =~ m|parser/analyze|)
{
$fnam = "parser/analyze";
}
else
{
die "$k1 - yet another analyze.c!";
}
}
$glob_glob->{fil2tab}->{$fnam} = {}
unless (exists($glob_glob->{fil2tab}->{$fnam}));
$glob_glob->{fil2tab}->{$fnam}->{$tname} = 1;
}
}
my $obyidx;
# Extract index for ORDER BY if specified
if ($qry =~ m/ORDER\_BY.*$/)
{
my @foo = ($qry =~ m/ORDER\_BY(.*)$/);
$obyidx = shift @foo;
# Trim to leave SELECT ... WHERE ...
$qry =~ s/ORDER\_BY.*$//;
$obyidx =~ s/^\s+//;
$obyidx =~ s/\s+$//;
}
if ($qry =~ m/where.*$/i)
{
my @foo = ($qry =~ m/where\s+(.*)\s*(?:\;)?/);
die "bad WHERE clause for $qerr" unless (scalar(@foo));
# print Data::Dumper->Dump(\@foo);
$where1 = shift @foo;
}
my (@wpred, @wpred2);
if (defined($where1))
{
my $kvrex =
# NOTE: allow more types of comparison than equality
#
# key = (or other comparison) :bindnum
'\s*(\w+)\s*(\=|\<\=|\>\=|\>|\<)\s*\:(\d+)\s*';
die "bad WHERE clause for: $qerr"
unless ($where1 =~ m/$kvrex/);
my @baz;
push @baz, $where1;
# build list of predicates if have "k1=v1 AND k2=v2..."
if ($where1 =~ m/\s+AND\s+/i)
{
@baz = split(/\s+AND\s+/i, $where1);
}
for my $pred (@baz)
{
# allow key = val, key <= val, key > val, etc
my @foo = ($pred =~ m/$kvrex/);
die "bad WHERE clause: $where1"
unless (3 == scalar(@foo));
$wkey = shift @foo;
$wcmp = shift @foo;
$wval = "arg" . shift @foo;
unless ($wcmp =~ m/^\=/)
{
$bAllEq = 0; # not an equality comparison
}
push @wpred, {key => $wkey, val => $wval, cmp => $wcmp};
}
}
die "no such tname $tname: $qerr" unless (exists($bigh->{$tname}));
my $realindex = "";
my $uniqueidx = 0; # index is unique
my $allcolidx = 0; # predicate uses all columns of index
my $numidxcols = 0; # number of columns in index
my $prefixidx; # primary key prefix index (if it exists)
my $syscacheid = "";
my $indexname = "InvalidOid"; # set to realindex if find it
my $fmgreqoid = "";
my $kvtransfm = "";
my $anumkeycol = "";
my $relationid = "";
my $wktyp;
if (exists($bigh->{$tname}->{CamelCaseRelationId}) &&
length($bigh->{$tname}->{CamelCaseRelationId}))
{
$relationid = $bigh->{$tname}->{CamelCaseRelationId};
}
else
{
die "no relationid $tname: $qerr";
}
$bqh->{tablename} = $tname; # for do_iud
if (exists($bigh->{$tname}->{fk_list}))
{
$bqh->{foreign_key_tables} = {}
unless (exists($bqh->{foreign_key_tables}));
for my $fk (@{$bigh->{$tname}->{fk_list}})
{
my $pktname = $fk->{pktable};
my $fkentry = "(" . join(", ", @{$fk->{pkcols}}) . ") <- (" .
join(", ", @{$fk->{fkcols}}) . ")";
$fkentry .= " [vector]"
if ($fk->{type} =~ m/vector/i);
# set of local fk cols for tname
$bqh->{foreign_key_tables}->{$pktname} = {}
unless (exists($bqh->{foreign_key_tables}->{$pktname}));
$bqh->{foreign_key_tables}->{$pktname}->{$fkentry} = 1;
}
}
if (scalar(@wpred))
{
for my $pred (@wpred)
{
$wkey = $pred->{key};
die "no type for $wkey! : $qerr"
unless (exists($bigh->{$tname}->{colh}->{$wkey}));
$anumkeycol = anum_key($tname, $wkey);
# oid column is system column (usually)
if (($wkey eq 'oid') &&
(exists($bigh->{$tname}->{with}->{oid})) &&
($bigh->{$tname}->{with}->{oid}))
{
$anumkeycol = 'ObjectIdAttributeNumber';
}
$wktyp = $bigh->{$tname}->{colh}->{$wkey};
# NOTE: convert regproc to oid, namedata to name
if ($wktyp =~ m/regproc/)
{
$wktyp = "oid";
}
# XXX XXX: CHECK THIS -- from gp_fastsequence
if ($wktyp =~ m/bigint/)
{
$wktyp = "int8";
}
$fmgreqoid = "F_" . uc($wktyp) . "EQ";
$fmgreqoid =~ s/NAMEDATA/NAME/;
if ($wktyp =~ m/oid/i)
{
$kvtransfm = "ObjectIdGetDatum";
}
if ($wktyp =~ m/name/i)
{
$kvtransfm = "NameGetDatum";
}
if ($wktyp =~ m/int2/i)
{
$kvtransfm = "Int16GetDatum";
}
$pred->{anumkeycol} = $anumkeycol;
$pred->{fmgreqoid} = $fmgreqoid;
$pred->{wktyp} = $wktyp;
$pred->{kvtransfm} = $kvtransfm;
} # end for my pred
my $iih = choose_index($bigh, $tname, \@wpred);
# if we had an ORDER BY, use that matching index instead
if (defined($obyidx))
{
$iih = check_oby_index($bigh, $tname, $obyidx, \@wpred);
}
if (defined($iih))
{
$realindex = $iih->{CamelCaseIndexId}
if (exists($iih->{CamelCaseIndexId}));
$syscacheid = $iih->{SysCacheIdentifier}
if (exists($iih->{SysCacheIdentifier}));
$uniqueidx = 1
if (exists($iih->{unique}));
$allcolidx = 1
if (exists($iih->{allcols}));
die "bad index -- no numcols"
unless (exists($iih->{numcols}));
$numidxcols = $iih->{numcols};
}
else
{
$realindex = undef;
$syscacheid = undef;
}
if (defined($realindex))
{
$bqh->{func_index} = $realindex;
}
else
{
$realindex = "";
}
# rebuild the WHERE predicate list. If we have an index,
# re-order it, else just use the original
if (!length($realindex))
{
push @wpred2, @wpred;
}
else
{
my $numkeys = scalar(@wpred);
my %pkeyh; # hash of key col names
for my $pred (@wpred)
{
$pkeyh{$pred->{key}} = $pred;
}
for my $inds (@{$bigh->{$tname}->{indexes}})
{
next
unless ($realindex eq $inds->{CamelCaseIndexId});
# NOTE: rebuild the WHERE clause predicate column list in
# index key column order
for my $jj (0..($numkeys-1))
{
my $icol = $inds->{cols}->[$jj];
$wpred2[$jj] = $pkeyh{$icol->[0]};
}
last;
}
}
# if this table is a "dependent class" of the parent table,
# then we can lock the primary key of the parent if we aren't
# using all the columns of our index.
# NOTE: first column must have equality comparison, but strict
# equality for all columns is not necessary
if (defined($iih) &&
($wpred2[0]->{cmp} =~ m/^\=/) &&
length($realindex) &&
!($uniqueidx && $allcolidx) &&
(exists($iih->{primarykey_prefix})))
{
$prefixidx = $iih->{primarykey_prefix};
$bqh->{func_note} .=
"index " . $prefixidx->{pktname} . "(" .
$prefixidx->{pkcolname} . ") <" .
$prefixidx->{pkidx} . "> is a prefix of \n\t" .
"index " . $tname . "(" .
$prefixidx->{fkcolname} . ", ...) <" .
$realindex . ">\n";
}
} # end if scalar wpred
my $funcargs = "cqContext *pCtx, cq_list *pcql, bool bLockEntireTable";
my $indexok = "pCtx->cq_useidxOK";
if (scalar(@wpred2))
{
if (length($realindex))
{
$indexname = $realindex;
}
else
{
$indexok = "false";
}
}
else
{
$indexok = "false";
}
# for optional arguments, strip out these lines from the final string
my $stripout = '/* **stripout** */';
# my $stripout = '';
my $striprex = quotemeta($stripout);
my $basicargs =
{
FUNCNAME => $funcname,
FUNCARGS => $funcargs,
INDEX => $indexname,
INDEXOK => $indexok,
UNIQUEIDX => $uniqueidx,
KEY => "pCtx->cq_scanKeys",
KEYNUM => scalar(@wpred2),
REL => "rel",
SCAN => "scan",
TUPLE => "tuple",
# SNAPSHOT => "SnapshotNow",
SNAPSHOT => "pCtx->cq_snapshot",
RELATIONID => $relationid,
RELATIONDEF => $stripout,
HEAPTUPLEDEF => $stripout,
OPENREL => $stripout,
CLOSEREL => $stripout,
KEYVAL => $wval
};
# turn on lock checking if lockcheck is on
$basicargs->{RELLOCKTEST} = (exists($glob_glob->{lockcheck}) &&
$glob_glob->{lockcheck}) ?
rel_lock_test() : $stripout;
my $scankeyinit = "";
my $readpk_init = ""; # readpk and lw_colstr for primary key readlock
my $lw_colstr = "";
if ($domethod !~ m/sel/i)
{
$basicargs->{HEAPTUPLEDEF} = "HeapTuple tuple;";
}
for my $ii (0..(scalar(@wpred2)-1))
{
my $pred2 = $wpred2[$ii];
my $currscankey;
$wkey = $pred2->{key};
$wval = $pred2->{val};
$wcmp = $pred2->{cmp};
$basicargs->{KEYOFFSET} = $ii;
$basicargs->{ANUMKEYCOL} = $pred2->{anumkeycol};
$basicargs->{FMGREQOID} = $pred2->{fmgreqoid};
$basicargs->{KVTRANSFORM} = $pred2->{kvtransfm};
# build type oid for caql_pkhash()
$basicargs->{MHTYPOID} = uc($pred2->{wktyp}) . "OID";
$basicargs->{MHTYPOID} =~ s/NAMEDATA/NAME/; # NAMEOID
if ($domethod =~ m/sel/i)
{
$basicargs->{KVTRANSFORM} = "";
$basicargs->{KEYVAL} = "pCtx->cq_datumKeys[$ii]";
}
# build a ScanKeyInit(...) statement
$currscankey =
doformat(scankeyinit(),
$basicargs
);
# fix comparison for inequality
unless ($wcmp =~ m/^\=/)
{
if ($wcmp =~ m/^\<\=/)
{
$currscankey =~
s/BTEqualStrategyNumber/BTLessEqualStrategyNumber/;
$currscankey =~ s/EQ\,/LE,/;
}
elsif ($wcmp =~ m/^\>\=/)
{
$currscankey =~
s/BTEqualStrategyNumber/BTGreaterEqualStrategyNumber/;
$currscankey =~ s/EQ\,/GE,/;
}
elsif ($wcmp =~ m/^\</)
{
$currscankey =~
s/BTEqualStrategyNumber/BTLessStrategyNumber/;
$currscankey =~ s/EQ\,/LT,/;
}
elsif ($wcmp =~ m/^\>/)
{
$currscankey =~
s/BTEqualStrategyNumber/BTGreaterStrategyNumber/;
$currscankey =~ s/EQ\,/GT,/;
}
}
# NOTE: special case for "primarykey_prefix"/"dependent class"
# -- build the caql_pkhash(...) statement for a single column
if (defined($prefixidx) &&
!length($lw_colstr))
{
# NOTE: only one column for this case, and we store the
# results in prefixidx (instead of readpk_init).
$prefixidx->{READPK_INIT} =
doformat(readpkeyhashinit(),
$basicargs
);
}
# build a "column string" for caql_lockwell()
$lw_colstr .= ", " if (length($lw_colstr));
$lw_colstr .= $wkey;
# initialize the hash for the primary key lock
# -- build the caql_pkhash(...) statement
$readpk_init .=
doformat(readpkeyhashinit(),
$basicargs
);
$scankeyinit .= $currscankey;
# XXX XXX: do something about stupid KEYVAL swizzling...
$basicargs->{KEYVAL} = $wval;
$scankeyinit .= "\n";
} # end for my ii
$scankeyinit .= "\n";
# store the ScanKeyInit(...) and caql_pkhash(...) initializations
$basicargs->{SCANKEYINIT} = $scankeyinit;
$basicargs->{READPK_INIT} = $readpk_init;
# special case if can use SysCache to satisfy query
$basicargs->{SYSCACHEIDENTIFIER} = $syscacheid;
$basicargs->{NUMSYSCACHEIDXCOLS} = $numidxcols;
if (!$bAllEq)
{
$bqh->{func_note} .= "WHERE clause is not strict equality\n";
if (defined($syscacheid) && length($syscacheid))
{
$bqh->{func_note} .= "Could not use syscache due to inequality\n";
}
}
else
{
# NOTE: even if WHERE clause is strict equality, syscache is
# only valid if predicate matches *all* columns of index,
# *or* if basic function is invoked by SearchSysCacheList()
if (defined($syscacheid) && length($syscacheid) && !$allcolidx)
{
$bqh->{func_note} .=
"Predicate does not match all index columns " .
"( " . scalar(@wpred2) . " != " . $numidxcols . " ),\n" .
"can only use syscache for SearchSysCacheList case\n";
}
}
# logging code
if ($glob_glob->{logquery} || $glob_glob->{logquery_hash})
{
$basicargs->{SELECTFROM_ELOG} =
doformat(selectfrom_elog(),
$basicargs,
);
}
else
{
$basicargs->{SELECTFROM_ELOG} = $stripout;
}
# set up read locks if possible
if (!(exists($glob_glob->{readlock}) && $glob_glob->{readlock}))
{
$basicargs->{GET_READPK} = $stripout;
$basicargs->{READPK_ENTIRETABLE} = $stripout;
$basicargs->{INSERTPK_ENTIRETABLE} = $stripout;
}
else # build read locks
{
my $bException = 0;
# XXX XXX NOTE: locking exceptions
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX: stupid last_operation/shoperation fixup, etc
$bException = check_locking_exception($tname);
# build caql_lock_entiretable(...) statement for insert.
#
# if the table has any primary key, just need a share lock on
# the entire table, else need an exclusive lock first
#
if (!$bException &&
($domethod =~ m/insert/i))
{
if (exists($bigh->{$tname}->{calico}) &&
exists($bigh->{$tname}->{calico}->{pkeys}) &&
(scalar(@{$bigh->{$tname}->{calico}->{pkeys}}) > 0))
{
# get share lock on entire table (if necessary)
$basicargs->{INSERTPK_ENTIRETABLE} =
doformat(readinsert_entiretable_share(),
$basicargs,
);
}
else
{
$basicargs->{READPK_FAILREASON} =
"no primary keys for $tname";
$basicargs->{INSERTPK_ENTIRETABLE} =
doformat(readinsert_entiretable_exclusive(),
$basicargs,
);
$bqh->{func_note} .=
"WARNING: insert operation not protected with pk lock!!\n";
$bigh->{$tname}->{calico}->{lock_entire_table} = 1;
}
}
if ($bException)
{
$basicargs->{READPK_FAILREASON} = "exception for $tname";
$basicargs->{GET_READPK} =
"/* no table lock - exception for $tname */\n";
$basicargs->{READPK_ENTIRETABLE} =
"/* no table lock - exception for $tname */\n";
$basicargs->{INSERTPK_ENTIRETABLE} =
"/* no table lock - exception for $tname */\n";
}
elsif (!defined($prefixidx) &&
(!$bAllEq || !length($readpk_init) ||
(!$uniqueidx) || (!$allcolidx)
))
{
if (!length($readpk_init) || !length($realindex))
{
$basicargs->{READPK_FAILREASON} = "no primary key index";
}
elsif (!$uniqueidx)
{
$basicargs->{READPK_FAILREASON} = "index not unique";
}
elsif (!$allcolidx)
{
$basicargs->{READPK_FAILREASON} = "partial match of index cols";
}
elsif (!$bAllEq)
{
$basicargs->{READPK_FAILREASON} = "inequality in index lookup";
}
$basicargs->{GET_READPK} =
doformat(readinsert_entiretable_exclusive(),
$basicargs,
);
# NOTE: don't consider "tablename" exceptions for locking
# case -- they are protected by other locks
if (($bqh->{num_upd_ops} || $bqh->{num_del_ops}) &&
!$bException &&
(!$bAllEq || !length($readpk_init) ||
(!$uniqueidx) || (!$allcolidx)
))
{
$bqh->{func_note} .=
"WARNING: update/delete operation not protected with pk lock!!\n";
$bigh->{$tname}->{calico}->{lock_entire_table} = 1;
}
}
else
{
my $real_iname = $basicargs->{INDEX};
my $real_relid = $basicargs->{RELATIONID};
# build caql_lock_entiretable(...) statement
$basicargs->{READPK_ENTIRETABLE} =
doformat(readinsert_entiretable_share(),
$basicargs,
);
if (!defined($prefixidx))
{
$basicargs->{LW_TNAME} = $tname;
$basicargs->{COLSTR} = $lw_colstr;
}
else
{
$basicargs->{INDEX} = $prefixidx->{pkidx};
$basicargs->{RELATIONID} = $prefixidx->{pkrelid};
$basicargs->{LW_TNAME} = $prefixidx->{pktname};
$basicargs->{COLSTR} = $prefixidx->{pkcolname};
$basicargs->{READPK_INIT} = $prefixidx->{READPK_INIT};
$bqh->{func_note} .= "Primary key Locking using index " .
$prefixidx->{pkidx} . " on " .
$prefixidx->{pktname} . "\n\tinstead of " .
$real_iname . "\n";
}
$basicargs->{GET_READPK} =
doformat(get_readpk(),
$basicargs,
);
# restore the index name and relation id
$basicargs->{INDEX} = $real_iname;
$basicargs->{RELATIONID} = $real_relid;
}
}
# can only use syscache for equality primary key lookup
if (defined($syscacheid) && length($syscacheid) && $bAllEq)
{
$bqh->{syscacheid} = $syscacheid;
$basicargs->{SYSCACHECHECK} =
doformat(syscacheid(),
$basicargs,
);
# and special tuple descriptors
$basicargs->{SYSCACHETUPDESC} =
doformat(syscachetupdesc(),
$basicargs,
);
}
else # must not use SysCache
{
$basicargs->{SYSCACHECHECK} =
doformat(nosyscacheid(),
$basicargs,
);
$basicargs->{SYSCACHETUPDESC} = "";
}
# add the indexOK check for non-heapscan case
if ($indexok !~ m/false/)
{
$basicargs->{SYSCACHECHECK} .=
doformat(idxokfunc(),
$basicargs,
);
}
$basicargs->{SCANHDR} =
doformat(scanhdr(),
$basicargs,
);
# cleanup whitespace in scanheader
$basicargs->{SCANHDR} =~ s/^\s*$striprex\s*$//gm;
$basicargs->{SCANHDR} =~ s/^(\s*)$striprex(.*)$/$1$2/gm;
$basicargs->{SCANHDR} =~ s/^(\s*)$//gm;
$basicargs->{BEGINSCAN} =
doformat(beginscan(),
$basicargs,
);
$basicargs->{NEXTSCAN} =
doformat(nextscan(),
$basicargs,
);
$basicargs->{ENDSCAN} =
doformat(endscan(),
$basicargs,
);
my $bigstr;
if ($domethod =~ m/sel/i)
{
$bigstr =
doformat(selectfrom(),
$basicargs
);
}
if ($domethod =~ m/del/i)
{
$bigstr =
doformat(deletefrom(),
$basicargs
);
}
elsif ($domethod =~ m/dup/i)
{
$bigstr =
doformat(duplicate_obj(),
$basicargs
);
}
elsif ($domethod =~ m/undef/i)
{
$bigstr =
doformat(undef_obj(),
$basicargs
);
}
elsif ($domethod =~ m/insert/i)
{
$bigstr =
doformat(insertinto(),
$basicargs
);
}
$bigstr =~ s/^\s*$striprex\s*$//gm;
$bigstr =~ s/^(\s*)$striprex(.*)$/$1$2/gm;
return $bigstr;
} # end dothing
# build insert/update/delete validation functions
sub do_iud
{
my ($bigh, $qry, $funcname, $bqh) = @_;
my @fktablist;
my @pktablist;
my @uniqidxlist; # uniq index list "indexname: col1, col2..."
my %uniqidxh; # hash by idx name
my $tname = $bqh->{tablename};
my $trelid = $bigh->{$tname}->{CamelCaseRelationId};
my $shlockwell_str = "/* No Share Locks Acquired */";
if (exists($bqh->{foreign_key_tables}))
{
for my $fktab (sort(keys(%{$bqh->{foreign_key_tables}})))
{
# XXX XXX NOTE: IUD Exceptions - fix these!!
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX: gp_relation_node
if ($fktab =~ m/(gp_relation_node)/)
{
next;
}
for my $fkentry (sort(keys(%{$bqh->{foreign_key_tables}->{$fktab}})))
{
push @fktablist,
$fktab . " " . $fkentry;
}
}
}
my $idxstr = "/* ZERO indexes */";
my $xlockwell_str = "/* Cannot obtain exclusive lock on tuple !! */";
if (exists($bigh->{$tname}))
{
# NOTE: built in bigh_fk_fixup()
if (exists($bigh->{$tname}->{pk_dependent}))
{
for my $pktab (sort(keys(%{$bigh->{$tname}->{pk_dependent}})))
{
# XXX XXX NOTE: IUD Exceptions - fix these!!
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX: gp_relation_node
if ($pktab =~ m/(gp_relation_node)/)
{
next;
}
# potentially multiple fk references to tname in
# dependent table
for my $pkl (sort(keys(%{$bigh->{$tname}->{pk_dependent}->{$pktab}})))
{
push @pktablist,
$pktab . " " . $pkl;
}
}
}
if (exists($bigh->{$tname}->{indexes}))
{
for my $idx (@{$bigh->{$tname}->{indexes}})
{
if (exists($idx->{unique})
&& length($idx->{unique})
&& ($idx->{unique}))
{
my $colstr;
my $cola = [];
$colstr = "";
for my $col (@{$idx->{cols}})
{
$colstr .= ", "
if (length($colstr));
$colstr .= $col->[0];
push @{$cola}, { name => $col->[0], ops => $col->[1] };
}
$colstr = $idx->{CamelCaseIndexId} . ": " . $colstr;
push @uniqidxlist, $colstr;
$uniqidxh{$idx->{CamelCaseIndexId}} = $cola;
}
}
if (scalar(@uniqidxlist))
{
$idxstr = "/*\n" .
scalar(@uniqidxlist) . " unique index";
# plural
$idxstr .=
((scalar(@uniqidxlist) > 1) ? "es:\n" : ":\n");
$idxstr .= join("\n", @uniqidxlist) . "\n*/";
$xlockwell_str = "";
for my $idxl (@uniqidxlist)
{
my $lockwellidxid;
#
# indexid:<space> key1[, key2...]
#
my @zzz = split(/\:\s*/, $idxl, 2);
die "bad index list: $idxl"
unless (2 == scalar(@zzz));
$lockwellidxid = $zzz[0];
$idxl = $zzz[1];
die "bad index: $lockwellidxid"
unless (exists($uniqidxh{$lockwellidxid}));
my $lw_makehash = "";
my $colinfo = $uniqidxh{$lockwellidxid};
for my $cola (@{$colinfo})
{
$lw_makehash .=
doformat(caql_makehash(),
{
MHTYPOID =>
colops2typoid($cola->{ops}),
MHATTNUM =>
(("oid" eq $cola->{name}) ?
'ObjectIdAttributeNumber' :
anum_key($tname, $cola->{name}))
}
);
}
my $lockwellh = {
# TABLERELID => "RelationGetRelid(pCtx->cq_heap_rel)",
TABLERELID => $bigh->{$tname}->{CamelCaseRelationId},
# LOCKMODE => "AccessExclusiveLock",
LOCKMODE => "pklockmode",
LW_TNAME => $tname,
COLSTR => $idxl,
LOCKWELL_IDX => $lockwellidxid,
LOCKWELL_MAKEHASH => $lw_makehash,
};
# build a string, but don't specify tuple
my $basic_lockwell_str .=
doformat(caql_iud_lockwell(),
$lockwellh);
# build two strings -- one for newtup, other for oldtup
$lockwellh->{TUPLE} = "newtup";
$lockwellh->{HASHVAR} = "newhash";
$lockwellh->{LOCKWELL_HASHKEY} = "newhash";
$lockwellh->{LOCKWELL_HASHKEY2} = "newhash";
my $l1 =
doformat($basic_lockwell_str,
$lockwellh);
$lockwellh->{TUPLE} = "oldtup";
$lockwellh->{HASHVAR} = "oldhash";
$lockwellh->{LOCKWELL_HASHKEY} = "oldhash";
$lockwellh->{LOCKWELL_HASHKEY2} = "oldhash";
my $l2 =
doformat($basic_lockwell_str,
$lockwellh);
$xlockwell_str .= $l1 . $l2;
} # end for my idxl
}
else
{
$idxstr = "/*\nZERO unique indexes\n*/";
}
}
}
my $rex1 = '(gp\_distribution\_policy)|(pg\_authid)';
# XXX XXX NOTE: IUD Exceptions
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX: stupid last_operation/shoperation fixup
if ($tname =~ m/pg\_stat\_last\_(sh)?operation/)
{
@fktablist = ();
@pktablist = ();
$xlockwell_str = "/* $tname: do not get exclusive lock */";
}
if ($tname =~ m/$rex1/)
{
$xlockwell_str = "/* $tname: do not get exclusive lock */";
}
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
if (scalar(@fktablist) &&
($tname !~ m/$rex1/))
{
$shlockwell_str = "";
for my $fktab1 (@fktablist)
{
my $fktab = $fktab1 . ""; # copy the string so we can modify it
# XXX XXX: special case for oidvector or oid array
my $isvector = ($fktab =~ m/\[vector\]/);
$fktab =~ s/\s*\[vector\]\s*//; # remove it
#
# tablename<space>(pk1[, pk2...]) <- (fk1[, fk2...])
#
my @foo = split(/\s+/, $fktab, 2); # first part is tablename
die "bad fktab: $fktab"
unless (2 == scalar(@foo));
# tricky nomenclature -- tname is our table, fktname is the
# foreign key table, but we need the primary key of that table.
my $fktname = $foo[0];
my $fkrelid = $bigh->{$fktname}->{CamelCaseRelationId};
#
# (pk1[, pk2...]) <- (fk1[, fk2...])
#
# get key list (pk/fk)
my @baz = split(/\s+\<\-\s+/, $foo[1], 2);
die "bad fktab2: $fktab"
unless (2 == scalar(@baz));
my $pk_colstr = $baz[0];
# remove lead/trailing parentheses
$pk_colstr =~ s/^\s*\(\s*//;
$pk_colstr =~ s/\s*\)\s*$//;
my @lockwell_keys;
my @ikeylist;
my $idxl = $baz[1];
# remove lead/trailing parentheses
$idxl =~ s/^\s*\(\s*//;
$idxl =~ s/\s*\)\s*$//;
if ($idxl =~ m/\,/)
{
@ikeylist = split(/\s*\,\s*/, $idxl)
}
else
{
# single key
push @ikeylist, $idxl;
}
# need the type of vector (oidvector or oid array) in
# order to distinguish the DatumGet<blah>() method in
# caql_iud_lockwell_oidvector()
my $isvector_type;
if ($isvector)
{
die "invalid vector has > 1 col"
unless (1 == scalar(@ikeylist));
# get the type by colname (only one for this case)
$isvector_type = $bigh->{$tname}->{colh}->{$ikeylist[0]};
}
# NOTE: 5 keys max
for my $ii (0..4)
{
if (defined($ikeylist[$ii]))
{
my $kk = $ikeylist[$ii];
$kk =~ s/^\s+//;
$kk =~ s/\s+$//;
if ($kk =~ m/^oid$/)
{
# Oid column
$kk = "ObjectIdAttributeNumber";
}
else
{
# attribute column
$kk = anum_key($tname, $kk);
}
push @lockwell_keys, $kk;
}
else
{
push @lockwell_keys, "InvalidAttrNumber";
}
}
# find the indexid for the unique index with those columns
my $lockwellidxid;
my $lockwellidxid_a =
unique_index_by_cols($bigh,
$fktname,
$pk_colstr
);
my $lw_makehash = "{ d = 0; isnull = 0; }";
if (defined($lockwellidxid_a))
{
my $colinfo = $lockwellidxid_a->[1];
$lockwellidxid = $lockwellidxid_a->[0];
$lw_makehash = "";
if (!$isvector)
{
for my $cola (@{$colinfo})
{
my $attnum = shift @lockwell_keys;
$lw_makehash .=
doformat(caql_makehash(),
{
MHTYPOID =>
colops2typoid($cola->{ops}),
MHATTNUM => $attnum
}
);
}
}
else
{
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# NOTE: the vector case is slightly funky --
# we build the LOCKWELL_MAKEHASH def for
# caql_iud_lockwell_oidvector(), but it's slightly
# different from the caql_makehash() definition
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
my $attnum = shift @lockwell_keys;
$lw_makehash .=
doformat(
"d = caql_getattr_internal(pCtx, {TUPLE}, \n" .
" {MHATTNUM},\n" .
" &isnull);\n" ,
{
MHATTNUM => $attnum
}
);
}
}
else
{
# no index found
$lockwellidxid = "InvalidOid";
}
my $lockwellh = {
TABLERELID => $fkrelid,
LOCKMODE => "AccessShareLock",
LW_TNAME => $fktname,
COLSTR => $pk_colstr,
LOCKWELL_IDX => $lockwellidxid,
LOCKWELL_MAKEHASH => $lw_makehash,
LW_GETDATUM => "DatumGetPointer", # for oidvector case
LW_VEC_OR_ARRAY => "vector", # for oidvector case.
};
# NOTE: oidvector is fixed length,
# but oid array *could* be toasted...
if ($isvector)
{
# if not oidvector, must be array (eg "Oid[1]")
if ($isvector_type !~ /vector/i)
{
$lockwellh->{LW_GETDATUM} = "DatumGetArrayTypeP";
$lockwellh->{LW_VEC_OR_ARRAY} = " array";
}
}
# build a string, but don't specify tuple
my $basic_lockwell_str .=
doformat(($isvector ?
caql_iud_lockwell_oidvector() :
caql_iud_lockwell()),
$lockwellh);
# build two strings -- one for newtup, other for oldtup
$lockwellh->{TUPLE} = "newtup";
$lockwellh->{HASHVAR} = "newhash";
$lockwellh->{LOCKWELL_HASHKEY} = "newhash";
$lockwellh->{LOCKWELL_HASHKEY2} = "newhash";
my $l1 =
doformat($basic_lockwell_str,
$lockwellh);
$lockwellh->{TUPLE} = "oldtup";
$lockwellh->{HASHVAR} = "oldhash";
$lockwellh->{LOCKWELL_HASHKEY} = "oldhash";
$lockwellh->{LOCKWELL_HASHKEY2} = "oldhash";
my $l2 =
doformat($basic_lockwell_str,
$lockwellh);
$shlockwell_str .= $l1 . $l2;
} # end for my fktab
}
my $bh_str = doformat(
caql_iud_buildhash(),
{
IUD_BH_TUP => "oldtup",
IUD_BH_VAL => "oldhash"
});
$bh_str .= doformat(
caql_iud_buildhash(),
{
IUD_BH_TUP => "newtup",
IUD_BH_VAL => "newhash"
});
my $iud_args = {
IUD_FUNC_NAME => $funcname,
IUD_TNAME => $tname,
IUD_BUILDHASH => $bh_str,
IUD_FKTABS => join("\n", @fktablist),
IUD_PKTABS => join("\n", @pktablist),
IUD_IDX => $idxstr,
IUD_IDX_XLOCKWELL => $xlockwell_str, # exclusive locks
IUD_SHLOCKWELL => $shlockwell_str, # share locks
};
$iud_args->{IUD_DELETE} = "";
# for INSERT only, don't construct a delete case
## if ($qry !~ m/\s*insert/i)
if (1) # XXX XXX XXX XXX XXX
{
if (scalar(@pktablist))
{
# have some foreign keys
$iud_args->{IUD_DELETE} =
doformat(caql_iud_delete(),
$iud_args);
$iud_args->{IUD_DELETE} .= "\n";
for my $pktab (@pktablist)
{
# XXX XXX:
next if ($pktab =~ m/\[vector\]/);
my $pkrelid = $bigh->{$pktab}->{CamelCaseRelationId};
# $iud_args->{IUD_DELETE} .= "gm = LockRelationOid_GetGrantMask($pkrelid, \"$funcname\");\n";
}
}
else
{
# none
}
}
$iud_args->{IUD_INSERTUPDATE} = "";
if (scalar(@fktablist))
{
$iud_args->{IUD_INSERTUPDATE} =
doformat(caql_iud_insertupdate(),
$iud_args);
$iud_args->{IUD_INSERTUPDATE} .= "\n";
$iud_args->{IUD_DELETE} =
"/* DELETE: no tables have fk reference to $tname */"
unless (scalar(@fktablist));
}
else
{
# none
if (scalar(@pktablist))
{
$iud_args->{IUD_INSERTUPDATE} =
"/* INSERT/UPDATE: $tname does not have fk reference to any table */"
}
else
{
$iud_args->{IUD_INSERTUPDATE} =
"/* INSERT/UPDATE/DELETE: no references\n $tname is unrelated to any other table */"
}
}
my $bigstr =
doformat(caql_iud_function(),
$iud_args);
return $bigstr;
} # end do_iud
sub do_interactive
{
my $bigh = shift;
while (<>)
{
my $ini = $_;
next unless (length($ini));
next if ($ini =~ m/^\s*$/);
print dothing($bigh, $ini);
}
}
sub check_bind_variables
{
my ($qry, $arglst) = @_;
use Text::ParseWords;
# find bind variables (:1 ... :N ), where max N = 5, if they
# exist
my @zzz = split(/(\:\d+)/, $qry);
my @bindlst;
for my $tok (@zzz)
{
next unless ($tok =~ m/^\:\d+$/);
$tok =~ s/\://;
if (($tok < 1) || ($tok > 5))
{
warn("bind variable $tok out of range (1-5)");
return 0;
}
if (defined($bindlst[$tok-1]))
{
warn("Duplicate bind variable $tok");
return 0;
}
$bindlst[$tok-1] = "CHECK_DUMMY";
}
for my $ii (1..(scalar(@bindlst)))
{
my $bb = $bindlst[$ii-1];
if (!defined($bb) ||
($bb !~ m/CHECK\_DUMMY/))
{
warn("missing bind variable $ii");
return 0;
}
}
my @args;
if (defined($arglst) && length($arglst) && ($arglst =~ m/\,/))
{
# split by comma, but use Text::ParseWords::parse_line to
# preserve quoted descriptions
@args = parse_line(",", 1, $arglst);
# Note: first comma is delimiter for *start* of arglst, so
# first arg should be NULL/blank
my $a1 = shift @args;
if (defined($a1) && ($a1 =~ m/\w/))
{
warn("weird first argument $a1");
return 0;
}
}
if (scalar(@args))
{
if ($args[0] =~ m/^\s*\".*\"/)
{
my $a2 = $args[0];
$a2 =~ s/^\s*//;
warn("Possible comma problem -- should first arg $a2 be part of query?");
return 0;
}
if (!scalar(@bindlst))
{
my $a2 = $args[0];
$a2 =~ s/^\s*//;
# XXX XXX: allow NULL as placeholder for non-existent bind
# vars to deal with compilation issues with cql->cql1 (ie,
# have a trailing comma preceding the non-existent
# varargs).
unless ($a2 =~ m/^NULL/)
{
warn("Found arg $a2 but no bind variables");
return 0;
}
}
}
# XXX XXX: Cannot do validation until return entire arglist
# (versus stopping at first end paren...)
if (scalar(@args) != scalar(@bindlst))
{
my $argnum = scalar(@args);
my $bindnum = scalar(@bindlst);
# print Data::Dumper->Dump(\@bindlst);
# print Data::Dumper->Dump(\@args);
# warn ("arg mismatch: $bindnum bind variables and $argnum arguments");
# return 0;
}
return 1;
} # end check_bind_variables
# extract caql from c source file
sub get_caql
{
my ($caqlh, $filnam, $bigh) = @_;
die "bad file $filnam" unless (-e $filnam);
my $whole_file;
{
# $$$ $$$ undefine input record separator (\n)
# and slurp entire file into variable
local $/;
undef $/;
my $fh;
open $fh, "< $filnam" or die "cannot open $filnam: $!";
$whole_file = <$fh>;
close $fh;
}
# find "cql(.*)", where ".*" is really "[not a close paren]*"
# NOTE: "count(*)" in the select list messes up this pattern, so
# replace it with a dummy expression, then extract the cql, then
# replace the dummy with "count(*)"
# my @foo = ($whole_file =~ m/(cql0\((?:[^\)])*\))/g);
# XXX XXX: Ugh! need to do this in a way to handle mixed case. Or
# complain or something
$whole_file =~ s/count\s*\(\s*\*\s*\)/CALICO_DUMMY_COUNTSTARR/g;
$whole_file =~ s/COUNT\s*\(\s*\*\s*\)/CALICO_DUMMY_COUNTSTAR2/g;
my @foo = ($whole_file =~ m/(cql(?:0)?\((?:[^\)])*\))/g);
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# NOTE: DOES NOT FIND full cql(.*) expression -- it may
# terminate at the closing paren for the first argument,
# eg: ObjectIdGetDatum(roleid)
# TODO: beef up the regex match to process the whole arg list.
# It's not as simple as looking for a trailing semicolon, since
# cql statements can be located in conditional expressions.
# XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX
# print Data::Dumper->Dump(\@foo);
my @baz;
for my $lin (@foo)
{
# $lin =~ s/\"\s*$^\s*"//gm;
# remove newlines and append contiguous quoted strings into a
# single string
$lin =~ s/\n//gm;
$lin =~ s/\"\s*\"//g;
# replace the dummy with "count(*)"
$lin =~ s/CALICO_DUMMY_COUNTSTARR/count\(\*\)/g;
$lin =~ s/CALICO_DUMMY_COUNTSTAR2/COUNT\(\*\)/g;
push @baz, $lin;
}
# print Data::Dumper->Dump(\@baz);
die "duplicate filename $filnam"
if (exists($caqlh->{files}->{$filnam}));
$caqlh->{files}->{$filnam}->{rawtxt} = \@foo;
$caqlh->{files}->{$filnam}->{cookedtxt} = \@baz;
# line should look like cql("<caql>"[, arg1 [,argN...]]
# NOTE: no trailing close paren ")"
for my $lin (@baz)
{
# my @zzz = ($lin =~ m/\s*cql0\(\"(.*)/);
my @zzz = ($lin =~ m/\s*cql(?:0)?\(\"(.*)\"/);
die "bad caql in $filnam: $lin"
unless (scalar(@zzz));
my $qry = shift @zzz;
# check if have sufficient/excess args, because C varargs is
# too dumb
@zzz = split(/\"/, $lin, 3);
# zzz[0] is 'cql("'
# zzz[1] is qry
my $arglst = $zzz[2];
die "bad caql in $filnam: $lin"
unless (check_bind_variables($qry, $arglst));
$caqlh->{queries}->{$qry} = { files => {}, cql => "cql0" }
unless (exists($caqlh->{queries}->{$qry}));
# detect case of pure cql0 queries
if ($caqlh->{queries}->{$qry}->{cql} eq "cql0")
{
$caqlh->{queries}->{$qry}->{cql} = "cql"
if ($lin =~ m/cql\(/);
}
if (exists($caqlh->{queries}->{$qry}->{files}->{$filnam}))
{
$caqlh->{queries}->{$qry}->{files}->{$filnam} += 1;
}
else
{
$caqlh->{queries}->{$qry}->{files}->{$filnam} = 1;
}
# extrapolate the "basic query" contained within the specified query
my $basic_qry = lc($qry);
$basic_qry =~ s/\s+/ /g;
# trim leading and trailing spaces
$basic_qry =~ s/^\s*//;
$basic_qry =~ s/\s*$//;
# tag the query as delete, count(*), for update
$caqlh->{queries}->{$qry}->{bDelete} =
($basic_qry =~ m/^delete/) ? 1 : 0;
$caqlh->{queries}->{$qry}->{bCount} =
($caqlh->{queries}->{$qry}->{bDelete}) ? 1 :
(($basic_qry =~ m/count\(\*\)/) ? 1 : 0);
$caqlh->{queries}->{$qry}->{bUpdate} =
($basic_qry =~ m/for update/) ? 1 : 0;
$caqlh->{queries}->{$qry}->{bInsert} =
($basic_qry =~ m/^insert/) ? 1 : 0;
die "bad query in $filnam: $qry -- cannot DELETE ... FOR UPDATE"
if ($caqlh->{queries}->{$qry}->{bDelete} &&
$caqlh->{queries}->{$qry}->{bUpdate});
die "bad query in $filnam: $qry -- cannot INSERT ... FOR UPDATE"
if ($caqlh->{queries}->{$qry}->{bInsert} &&
$caqlh->{queries}->{$qry}->{bUpdate});
# strip out delete, count(*), for update attributes
$basic_qry =~ s/^select\s+count\(\*\)/select */;
$basic_qry =~ s/^delete/select */;
# temporarily treat INSERT as SELECT * (but reverse later)
$basic_qry =~ s/^insert\s+into/select * from/;
# SELECT ... ORDER BY ... FOR UPDATE
$basic_qry =~ s/\s*for update\s*$//;
if ($basic_qry =~ m/order\s+by/)
{
my @oby = ($basic_qry =~ m/order\s+by\s+(.*)/);
die "bad query in $filnam: $qry -- invalid ORDER BY"
unless (scalar(@oby));
$caqlh->{queries}->{$qry}->{oby} = { rawtxt => $oby[0] };
$basic_qry =~ s/\s*order\s+by\s*.*$//;
}
# check for a column name in SELECT list
# NOTE: only support a single column currently
$caqlh->{queries}->{$qry}->{colnum} = "InvalidAttrNumber";
if ($basic_qry =~ m/^select\s+(.*)\s+from/i)
{
my $tname;
if ($basic_qry =~ m/^select\s+\*\s+from/i)
{
my @ccc =
($basic_qry =~ m/^select\s+\*\s+from\s+(\w*)\s*/i);
$tname = shift @ccc;
$caqlh->{queries}->{$qry}->{tablename} = $tname;
}
else
{
my @ccc =
($basic_qry =~ m/^select\s+(\w*)\s+from\s+(\w*)\s+/i);
my $colname = shift @ccc;
$tname = shift @ccc;
$caqlh->{queries}->{$qry}->{colname} = $colname;
$caqlh->{queries}->{$qry}->{tablename} = $tname;
die "no type for $colname! : $filnam: $qry"
unless (exists($bigh->{$tname}->{colh}->{$colname}));
$caqlh->{queries}->{$qry}->{colnum} =
anum_key($tname, $colname);
# oid column is system column (usually)
if (($colname eq 'oid') &&
(exists($bigh->{$tname}->{with}->{oid})) &&
($bigh->{$tname}->{with}->{oid}))
{
$caqlh->{queries}->{$qry}->{colnum} =
'ObjectIdAttributeNumber';
}
# remove the column name
$basic_qry =~ s/^select\s+.*\s+from/select \* from/;
}
# process ORDER BY cols (if any)
if (exists($caqlh->{queries}->{$qry}->{oby}))
{
my $rawoby = $caqlh->{queries}->{$qry}->{oby}->{rawtxt};
$rawoby =~ s/^\s+//;
$rawoby =~ s/\s+$//;
my @obylist;
if ($rawoby !~ m/\,/)
{
push @obylist, $rawoby;
}
else
{
@obylist = split(/\s*\,\s*/, $rawoby);
}
die "bad query in $filnam: $qry -- invalid ORDER BY"
unless (scalar(@obylist));
# get ORDER BY column names and attribute numbers
my $obnams = []; # list of names
my $obnums = []; # list of attribute numbers
my $obpred = []; # predicate list for choose_index()
for my $obyitem (@obylist)
{
$obyitem =~ s/^\s+//;
$obyitem =~ s/\s+$//;
die "no type for ORDER BY $obyitem! : $filnam: $qry"
unless (exists($bigh->{$tname}->{colh}->{$obyitem}));
push @{$obnams}, $obyitem;
push @{$obpred}, {key =>$ obyitem};
push @{$obnums}, anum_key($tname, $obyitem);
}
$caqlh->{queries}->{$qry}->{oby}->{colnames} = $obnams;
$caqlh->{queries}->{$qry}->{oby}->{attnums} = $obnums;
# choose an _exact_ match index (columns match ORDER BY)
my $obyidx = choose_index($bigh, $tname, $obpred, 1);
die "no index for ORDER BY! : $filnam: $qry"
unless (defined($obyidx));
$caqlh->{queries}->{$qry}->{oby}->{index} = $obyidx;
# NOTE: Add ORDER BY to distinguish basic query with
# required order from same basic query with optional
# order.
$basic_qry .= " ORDER_BY " . $obyidx->{CamelCaseIndexId};
}
} # end if select
die "bad query in $filnam: $qry"
unless ($basic_qry =~ m/^select\s+\*\s+from/i);
# fix the basic query to show INSERT
$basic_qry =~ s/^select\s+\*\s+from/insert into/i
if ($caqlh->{queries}->{$qry}->{bInsert});
$caqlh->{queries}->{$qry}->{basic} = $basic_qry;
$caqlh->{basic}->{$basic_qry} = { files => {} ,
cql => "cql0",
indexes => {},
num_ins_ops => 0,
num_upd_ops => 0,
num_del_ops => 0 }
unless (exists($caqlh->{basic}->{$basic_qry}));
# track insert/update/delete operations
$caqlh->{basic}->{$basic_qry}->{num_ins_ops} += 1
if ($caqlh->{queries}->{$qry}->{bInsert});
$caqlh->{basic}->{$basic_qry}->{num_upd_ops} += 1
if ($caqlh->{queries}->{$qry}->{bUpdate});
$caqlh->{basic}->{$basic_qry}->{num_del_ops} += 1
if ($caqlh->{queries}->{$qry}->{bDelete});
# detect case of pure cql0 queries
if ($caqlh->{basic}->{$basic_qry}->{cql} eq "cql0")
{
$caqlh->{basic}->{$basic_qry}->{cql} = "cql"
if ($caqlh->{queries}->{$qry}->{cql} eq "cql")
}
# track file references for this query
if (exists($caqlh->{basic}->{$basic_qry}->{files}->{$filnam}))
{
$caqlh->{basic}->{$basic_qry}->{files}->{$filnam} += 1;
}
else
{
$caqlh->{basic}->{$basic_qry}->{files}->{$filnam} = 1;
}
} # end for my lin
} # end get_caql
sub gperf_header
{
my $bigstr = <<'EOF_bigstr';
%{
{GENERAL_HDR}
%}
struct caql_hash_cookie
{
const char *name; /* caql string */
int uniqquery_code; /* corresponding unique query */
int basequery_code; /* corresponding base query */
int bDelete; /* query performs DELETE */
int bCount; /* SELECT COUNT(*) (or DELETE) */
int bUpdate; /* SELECT ... FOR UPDATE */
int bInsert; /* INSERT INTO */
AttrNumber attnum; /* column number (or 0 if no column specified) */
};
%%
EOF_bigstr
return $bigstr;
}
sub more_header
{
my $bigstr = <<'EOF_bigstr';
/*-------------------------------------------------------------------------
*
* catquery.c
* general catalog table access methods (internal api)
*
* 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.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <math.h>
#include <fcntl.h>
#include <locale.h>
#include <string.h>
#include <unistd.h>
#include "access/genam.h"
#include "access/heapam.h"
#include "access/relscan.h"
#include "access/transam.h"
#include "catalog/caqlparse.h"
#include "catalog/catalog.h"
#include "catalog/catquery.h"
#include "catalog/indexing.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_appendonly_alter_column.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_autovacuum.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_class.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_database.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_description.h"
#include "catalog/pg_extprotocol.h"
#include "catalog/pg_exttable.h"
#include "catalog/pg_filespace.h"
#include "catalog/pg_filespace_entry.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_language.h"
#include "catalog/pg_largeobject.h"
#include "catalog/pg_listener.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_partition.h"
#include "catalog/pg_partition_rule.h"
#include "catalog/pg_pltemplate.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_resqueue.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shdepend.h"
#include "catalog/pg_shdescription.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_user_mapping.h"
#include "catalog/pg_window.h"
#include "catalog/pg_tidycat.h"
#include "catalog/gp_configuration.h"
#include "catalog/gp_configuration.h"
#include "catalog/gp_segment_config.h"
#include "catalog/gp_san_config.h"
#include "catalog/gp_master_mirroring.h"
#include "catalog/gp_persistent.h"
#include "catalog/gp_global_sequence.h"
#include "catalog/gp_version.h"
#include "catalog/toasting.h"
#include "catalog/gp_policy.h"
#include "miscadmin.h"
#include "storage/fd.h"
#include "utils/fmgroids.h"
#include "utils/relcache.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/inval.h"
#include "cdb/cdbpersistenttablespace.h"
#include "cdb/cdbvars.h"
{STATIC_CAQL_LOCKWELL}
{CAQL_LOG_HASH}
/* ----------------------------------------------------------------
* cq_lookup()
* cq_lookup() defines a hash cookie for every cql() declaration. The
* cookie associates the caql string with a "base query" function
* [caql_basic_fn_#()] that constructs the scan for the query.
* caql_switch() dispatches on the cookie to the base query function.
* ----------------------------------------------------------------
*/
EOF_bigstr
return $bigstr;
} # end more_header
sub caql_switch
{
my $bigstr = <<'EOF_bigstr';
/* ----------------------------------------------------------------
* caql_switch()
* Given a cookie, dispatch to the appropriate "base query" function to
* construct the scan, and return a cqContext
* NOTE: the caql_switch frees the cql after it sets up the pCtx
* ----------------------------------------------------------------
*/
static
cqContext *caql_switch(struct caql_hash_cookie *pchn,
cqContext *pCtx,
cq_list *pcql)
{
Assert(pCtx); /* must have a valid context */
/* set the snapshot and lockmodes */
if (!pCtx->cq_setsnapshot)
pCtx->cq_snapshot = SnapshotNow;
if (!pCtx->cq_setlockmode)
{
if (pchn->bDelete || pchn->bUpdate || pchn->bInsert)
pCtx->cq_lockmode = RowExclusiveLock;
else
pCtx->cq_lockmode = AccessShareLock;
}
/* XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX */
if (1) /* implicit locking everywhere */
{
pCtx->cq_setpklock = 1;
pCtx->cq_pklock_excl =
(pchn->bDelete || pchn->bUpdate || pchn->bInsert);
}
/* XXX XXX XXX XXX XXX XXX XXX XXX XXX XXX */
/* pcql must be valid */
Assert(pcql && pcql->bGood);
/* get everything we need from cql */
for (int ii = 0; ii < pcql->maxkeys; ii++)
{
pCtx->cq_datumKeys[ii] = pcql->cqlKeys[ii];
}
pCtx->cq_NumKeys = pcql->maxkeys;
pCtx->cq_cacheKeys = &pCtx->cq_datumKeys[0];
pCtx->cq_uniqquery_code = pchn->uniqquery_code;
pCtx->cq_basequery_code = pchn->basequery_code;
/* NOTE: pass the cql to basic functions -- optionally
* used for debugging
*/
switch(pchn->basequery_code)
{
{ALLCASES}
default:
break;
/* bad */
}
/* NOTE: free up the cql before we return */
pcql->bGood = false;
pfree(pcql);
return (pCtx);
} /* end caql_switch */
EOF_bigstr
return $bigstr;
}
sub caql_switch_case
{
my $bigstr = <<'EOF_bigstr';
case {CASEVAL}: /* {CASECOMM} */
pCtx->cq_sysScan = {FUNCNAME}(pCtx, pcql, {IS_LOCKENTIRETABLE});
break;
EOF_bigstr
return $bigstr;
}
sub caql_iud_switch
{
my $bigstr = <<'EOF_bigstr';
/* ----------------------------------------------------------------
* caql_iud_switch()
* dispatch to the appropriate "insert/update/delete" function
* for concurrent update validation
* ----------------------------------------------------------------
*/
static
void caql_iud_switch(cqContext *pCtx, int is_iud,
HeapTuple oldtup, HeapTuple newtup, bool dontWait)
{
LOCKMODE pklockmode = AccessExclusiveLock;
Assert(pCtx); /* must have a valid context */
if (pCtx->cq_setpklock)
{
/* WAIT if caql_PKLOCK() is set */
dontWait = false;
if (!pCtx->cq_pklock_excl)
pklockmode = AccessShareLock;
}
switch(pCtx->cq_basequery_code)
{
{ALL_IUD_CASES}
default:
break;
/* bad */
}
} /* end caql_iud_switch */
EOF_bigstr
return $bigstr;
} # end caql_iud_switch
sub caql_iud_switch_case
{
my $bigstr = <<'EOF_bigstr';
case {CASEVAL}: /* {CASECOMM} */
{IUD_FUNC_NAME}(pCtx, is_iud, oldtup, newtup, dontWait, pklockmode);
break;
EOF_bigstr
return $bigstr;
}
sub caql_iud_delete
{
my $bigstr = <<'EOF_bigstr';
/*
if deleting, {IUD_TNAME} primary key may be referenced in:
{IUD_PKTABS}
*/
EOF_bigstr
return $bigstr;
}
sub caql_iud_insertupdate
{
my $bigstr = <<'EOF_bigstr';
/*
if insert/update, check foreign keys against:
{IUD_FKTABS}
*/
EOF_bigstr
return $bigstr;
}
sub caql_makehash
{
my $bigstr = <<'EOF_bigstr';
d = caql_getattr_internal(pCtx, {TUPLE}, {MHATTNUM}, &isnull);
{HASHVAR} = caql_pkhash(pCtx, {HASHVAR}, d, isnull, {MHTYPOID});
EOF_bigstr
return $bigstr;
} # end caql_makehash
sub static_caql_log_hash_create
{
my $bigstr = <<'EOF_bigstr';
if (gp_enable_caql_logging && CaQLLogHash == NULL)
{
HASHCTL hash_ctl;
hash_ctl.keysize = sizeof(CaQLLogTag);
hash_ctl.entrysize = sizeof(CaQLLogTag);
hash_ctl.hash = tag_hash;
CaQLLogHash = hash_create("caql log hash",
1000,
&hash_ctl,
HASH_ELEM | HASH_FUNCTION);
}
EOF_bigstr
return $bigstr;
}
sub static_caql_log_hash
{
my $bigstr = <<'EOF_bigstr';
static HTAB *CaQLLogHash = NULL;
typedef struct CaQLLogTag
{
char filename[MAXPGPATH];
int lineno;
} CaQLLogTag;
typedef struct CaQLLogEntry
{
CaQLLogTag key;
} CaQLLogEntry;
static uint32 caqlLogHashCode(CaQLLogTag *tagPtr)
{
return get_hash_value(CaQLLogHash, (void *)tagPtr);
}
EOF_bigstr
return $bigstr;
} # end static_caql_log_hash
sub static_caql_lockwell
{
my $bigstr = <<'EOF_bigstr';
static void caql_lockwell(cqContext *pCtx,
Oid relid,
LOCKMODE lockmode,
HeapTuple tup,
char *tablenbame,
char *colstr,
Oid indexid,
Oid hashoid,
bool dontWait,
bool ignoreInvalidTuple
);
static Oid caql_pkhash(cqContext *pCtx,
Oid hashoid,
Datum d,
bool isNull,
Oid typoid
);
static void caql_lock_entiretable(cqContext *pCtx,
Oid relid,
LOCKMODE lockmode,
bool dontWait
);
EOF_bigstr
return $bigstr;
} # end static_caql_lockwell
sub caql_heapclose_releaselock
{
my $bigstr = <<'EOF_bigstr';
heap_close((pCtx)->cq_heap_rel, (pCtx)->cq_lockmode); \
EOF_bigstr
} # end caql_heapclose_releaselock
# if not in a transaction, release the table lock, else hold it
# (until the txn commits/aborts)
sub caql_heapclose_holdlock
{
my $bigstr = <<'EOF_bigstr';
{ if (!IsTransactionState()) \
heap_close((pCtx)->cq_heap_rel, (pCtx)->cq_lockmode); \
else heap_close((pCtx)->cq_heap_rel, NoLock); } \
EOF_bigstr
} # end caql_heapclose_holdlock
# build the C code for the caql fetch functions (getcount(), getfirst(), etc)