Building and Distributing     PostgreSQL Extensions      Without Learning C                                               ...
There arequite a fewPostgreSQLExtensions.
Core Extensions
Core Extensionsintagg             pgcryptointarray           btree_ginhstore             chkpasscitext             dblinkc...
Common Extensions
Common ExtensionsPostGIS      PL/Rtemporal     OracleFCEpgmemcache   mysqlcompatPL/Proxy     pg_randomnessPL/Java      pgT...
You can createextensions, too!
I know whatyou’rethinking…
“Sure, but I get C sick.”
Fortunately…
There aremany waysto extendPostgreSQL.
Functionstruth.sql
FunctionsCREATE
FUNCTION
truthy(



text)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS
$$



SELECT
$1
IS
NOT
NULL






AND
...
FunctionsCREATE
FUNCTION
truthy(



text)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS
$$



SELECT
$1
IS
NOT
NULL






AND
...
FunctionsCREATE
FUNCTION
truthy(



text)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS
$$



SELECT
$1
IS
NOT
NULL






AND
...
FunctionsCREATE
FUNCTION
truthy(



text)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS
$$



SELECT
$1
IS
NOT
NULL






AND
...
FunctionsCREATE
FUNCTION
truthy(



text)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS
$$



SELECT
$1
IS
NOT
NULL






AND
...
FunctionsCREATE
FUNCTION
truthy(



text)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS
$$



SELECT
$1
IS
NOT
NULL






AND
...
Domainstimezone.sql
DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM...
DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM...
DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM...
DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM...
DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM...
DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM...
DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM...
DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM...
Typespair.sql
TypesCREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
store(



params
variadic
pair[])
RETURNS
VOID
LANG...
TypesCREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
store(



params
variadic
pair[])
RETURNS
VOID
LANG...
TypesCREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
store(



params
variadic
pair[])
RETURNS
VOID
LANG...
TypesCREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
store(



params
variadic
pair[])
RETURNS
VOID
LANG...
TypesCREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
store(



params
variadic
pair[])
RETURNS
VOID
LANG...
Operatorstruthy.sql
OperatorsCREATE
OPERATOR
@
(
 RIGHTARG

=
text,
 PROCEDURE
=
truthy); truthy.sql
OperatorsCREATE
OPERATOR
@
(
 RIGHTARG

=
text,
 PROCEDURE
=
truthy); truthy.sql
OperatorsCREATE
OPERATOR
@
(
 RIGHTARG

=
text,
 PROCEDURE
=
truthy); truthy.sql
OperatorsCREATE
OPERATOR
@
(
 RIGHTARG

=
text,
 PROCEDURE
=
truthy);SELECT
@foo;
‐‐
trueSELECT
@;



‐‐
false truthy.sql
OperatorsCREATE
OPERATOR
@
(
 RIGHTARG

=
text,
 PROCEDURE
=
truthy);SELECT
@foo;
‐‐
trueSELECT
@;



‐‐
false truthy.sql ...
PostgreSQLis not merelya database…
…It’s anapplicationdevelopmentplatform.
Developreusableextensionlibraries…
And combinethem whenbuilding apps.
Let’s create anextension anddistribute it.
The Steps
The StepsStub source, test, and doc files
The StepsStub source, test, and doc filesCreate the Makefile
The StepsStub source, test, and doc filesCreate the MakefileWrite the tests
The StepsStub source, test, and doc filesCreate the MakefileWrite the testsWrite the code
The StepsStub source, test, and doc filesCreate the MakefileWrite the testsWrite the codeWrite the docs
The StepsStub source, test, and doc filesCreate the MakefileWrite the testsWrite the codeWrite the docsCreate the metadata file
The StepsStub source, test, and doc filesCreate the MakefileWrite the testsWrite the codeWrite the docsCreate the metadata fi...
The StepsStub source, test, and doc filesCreate the MakefileWrite the testsWrite the codeWrite the docsCreate the metadata fi...
Stub the Sourcesql/pair.sql
Stub the SourceSET
client_min_messages
=
warning;‐‐
Create
the
type
here. sql/pair.sql
Stub the SourceSET
client_min_messages
=
warning;‐‐
Create
the
type
here. sql/pair.sql
Stub the SourceSET
client_min_messages
=
warning;‐‐
Create
the
type
here. sql/pair.sql
Stub the SourceSET
client_min_messages
=
warning;‐‐
Create
the
type
here.                    sql/pair.sql sql/pair.sql
Stub the Teststest/sql/base.sql
Stub the TestsBEGIN;set
ECHO
0set
QUIET
1i
sql/pair.sqlset
QUIET
0ROLLBACK; test/sql/base.sql
Stub the TestsBEGIN;set
ECHO
0set
QUIET
1i
sql/pair.sqlset
QUIET
0ROLLBACK; test/sql/base.sql
Stub the TestsBEGIN;set
ECHO
0set
QUIET
1i
sql/pair.sqlset
QUIET
0ROLLBACK;     test/sql/base.sql test/sql/base.sql
Stub the Docstest/sql/base.sql
Stub the Docspair
0.1.0==========Synopsis‐‐‐‐‐‐‐‐



‐‐
Example
code
here.



Description‐‐‐‐‐‐‐‐‐‐‐This
library
blah
blah...
Stub the Docspair
0.1.0                                  doc/pair.txt==========Synopsis‐‐‐‐‐‐‐‐



‐‐
Example
code
here.

...
Create the MakefileMakefile
Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*....
Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*....
Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*....
Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*....
Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*....
Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*....
Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*....
Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*....
Create the Makefile                   inputdirDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsu...
Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*....
Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*....
Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*....
Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*....
Give it a Try%

Give it a Try

make%
make:
Nothing
to
be
done
for
`all.%
Give it a Try

make%
make:
Nothing
to
be
done
for
`all.

make
installcheck%pg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test...
Give it a Try

make%
make:
Nothing
to
be
done
for
`all.

make
installcheck%pg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test...
Give it a Try

make%
make:
Nothing
to
be
done
for
`all.

make
installcheck%pg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test...
Write Some Testsi
sql/pair.sqlset
QUIET
0ROLLBACK; test/sql/base.sql
Write Some Testsi
sql/pair.sqlset
QUIET
0





SELECT
pair(foo,
bar)UNION
SELECT
pair(HEY::text,
bar)UNION
SELECT
pair(foo...
Write Some Testsi
sql/pair.sqlset
QUIET
0





SELECT
pair(foo,
bar)UNION
SELECT
pair(HEY::text,
bar)UNION
SELECT
pair(foo...
Write Some Testsi
sql/pair.sqlset
QUIET
0





SELECT
pair(foo,
bar)UNION
SELECT
pair(HEY::text,
bar)UNION
SELECT
pair(foo...
Write Some Testsi
sql/pair.sqlset
QUIET
0





SELECT
pair(foo,
bar)UNION
SELECT
pair(HEY::text,
bar)UNION
SELECT
pair(foo...
Run Them%

Run Them%


psql
‐d
try
‐Xf
test/sql/base.sqlBEGINpsql:test/sql/base.sql:15:
ERROR:

function
pair(unknown,
unknown)
does
...
Run Them%


psql
‐d
try
‐Xf
test/sql/base.sqlBEGINpsql:test/sql/base.sql:15:
ERROR:

function
pair(unknown,
unknown)
does
...
SET
client_min_messages
=
warning;‐‐
Create
the
type
here.  sql/pair.sql
SET
client_min_messages
=
warning;CREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)...
SET
client_min_messages
=
warning;CREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)...
SET
client_min_messages
=
warning;CREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)...
SET
client_min_messages
=
warning;CREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)...
SET
client_min_messages
=
warning;CREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)...
SET
client_min_messages
=
warning;CREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)...
%
%

psql
‐d
try
‐Xf
test/sql/base.sqlBEGIN



pair



‐‐‐‐‐‐‐‐‐‐‐‐
(1,12)
(12.3,foo)
(HEY,bar)
(foo,1)
(foo,bar)
(foo,woah)...
Moar Tests!UNION
SELECT
pair(12.3,
foo::text)UNION
SELECT
pair(1,
12)ORDER
BY
pair;ROLLBACK; test/sql/base.sql
Moar Tests!UNION
SELECT
pair(12.3,
foo::text)UNION
SELECT
pair(1,
12)ORDER
BY
pair;





SELECT
foo
~>
bar
AS
arrowopUNION...
Moar Tests!UNION
SELECT
pair(12.3,
foo::text)UNION
SELECT
pair(1,
12)ORDER
BY
pair;





SELECT
foo
~>
bar
AS
arrowopUNION...
Moar Tests!UNION
SELECT
pair(12.3,
foo::text)UNION
SELECT
pair(1,
12)ORDER
BY
pair;





SELECT
foo
~>
bar
AS
arrowopUNION...
Moar Tests!UNION
SELECT
pair(12.3,
foo::text)UNION
SELECT
pair(1,
12)ORDER
BY
pair;





SELECT
foo
~>
bar
AS
arrowopUNION...
%
%

psql
‐d
try
‐Xf
test/sql/base.sqlBEGIN



pair



‐‐‐‐‐‐‐‐‐‐‐‐
(1,12)
(12.3,foo)
(HEY,bar)
(foo,1)
(foo,bar)
(foo,woah)...

(foo,1)
(foo,bar)%
(foo,woah)
(ick,foo)(7
rows)psql:test/sql/base.sql:25:
ERROR:

operator
unknown
~>
unknownLINE
1:
SELE...
CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;;  sql/pair.sql
CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;;CREATE
OPERATOR
~>
(
   ...
CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;;CREATE
OPERATOR
~>
(
   ...
CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;;CREATE
OPERATOR
~>
(
   ...
CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;;CREATE
OPERATOR
~>
(
   ...
CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;;CREATE
OPERATOR
~>
(
   ...
%
%

psql
‐d
try
‐Xf
test/sql/base.sqlBEGIN



pair



‐‐‐‐‐‐‐‐‐‐‐‐
(1,12)
(12.3,foo)
(HEY,bar)
(foo,1)
(foo,bar)
(foo,woah)...

(foo,bar)
(foo,woah)%
(ick,foo)(7
rows)

arrowop


‐‐‐‐‐‐‐‐‐‐‐‐
(1,12)
(12.3,foo)
(HEY,bar)
(foo,1)
(foo,bar)
(foo,woah)
...
One More Time!UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;ROLLBACK; test/sql/base.sql
One More Time!UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;CREATE
TABLE
kv
(



pair
pair);INSERT
IN...
One More Time!UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;CREATE
TABLE
kv
(



pair
pair);INSERT
IN...
One More Time!UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;CREATE
TABLE
kv
(



pair
pair);INSERT
IN...
One More Time!UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;CREATE
TABLE
kv
(



pair
pair);INSERT
IN...
One More Time!UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;CREATE
TABLE
kv
(



pair
pair);INSERT
IN...
One More Time!UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;CREATE
TABLE
kv
(



pair
pair);INSERT
IN...
%
%

psql
‐d
try
‐Xf
test/sql/base.sqlBEGIN



pair



‐‐‐‐‐‐‐‐‐‐‐‐
(1,12)
(12.3,foo)
(HEY,bar)
(foo,1)
(foo,bar)
(foo,woah)...
Remember This?%

Remember This?%


make
installcheckpg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test
base===
dropping
database
"regression"
...
Let’s Fix That%

Let’s Fix That%


echo
set
ECHO
0
>
test/expected/base.out
%
Let’s Fix That%


echo
set
ECHO
0
>
test/expected/base.out


psql
‐d
try
‐Xf
test/sql/base.sql
%

|
grep
‐v
^BEGIN
>>
test...
Let’s Fix That%


echo
set
ECHO
0
>
test/expected/base.out


psql
‐d
try
‐Xf
test/sql/base.sql
%

|
grep
‐v
^BEGIN
>>
test...
Let’s Fix That%


echo
set
ECHO
0
>
test/expected/base.out


psql
‐d
try
‐Xf
test/sql/base.sql
%

|
grep
‐v
^BEGIN
>>
test...
Where are We?Stub source, test, and doc filesCreate the MakefileWrite the testsWrite the codeWrite the docsCreate the metada...
Where are We?✔   Stub source, test, and doc files    Create the Makefile    Write the tests    Write the code    Write the d...
Where are We?✔   Stub source, test, and doc files✔   Create the Makefile    Write the tests    Write the code    Write the d...
Where are We?✔   Stub source, test, and doc files✔   Create the Makefile✔   Write the tests    Write the code    Write the d...
Where are We?✔   Stub source, test, and doc files✔   Create the Makefile✔   Write the tests✔   Write the code    Write the d...
Write the Docspair
0.1.0==========Synopsis‐‐‐‐‐‐‐‐



‐‐
Example
code
here.Description‐‐‐‐‐‐‐‐‐‐‐This
library
blah
blah
bl...
Write the Docspair
0.1.0==========Synopsis‐‐‐‐‐‐‐‐



%
CREATE
EXTENSION
pair;



CREATE
EXTENSION



%
SELECT
pair(foo,
b...
Write the DocsDescription‐‐‐‐‐‐‐‐‐‐‐This
library
blah
blah
blah...Usage‐‐‐‐‐Heres
how
to
use
this
library.Author‐‐‐‐‐‐[Dav...
Write the DocsDescription‐‐‐‐‐‐‐‐‐‐‐This
library
contains
a
single
PostgreSQL
extension,
a
key/value
pair
datatype
called
...
META.jsonMETA.json
META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
...
META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
...
META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
...
META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
...
META.json{                   semver.org


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":...
META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
...
META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
...
META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
...
META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
...
META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
...
Package it Up!%

Package it Up!%


git
archive
‐‐format
zip
‐‐prefix=pair‐0.1.0/


‐‐output
~/Desktop/pair‐0.1.0.zip
master%
Package it Up!%


git
archive
‐‐format
zip
‐‐prefix=pair‐0.1.0/


‐‐output
~/Desktop/pair‐0.1.0.zip
master%               ...
Where are We?✔   Stub source, test, and doc files✔   Create the Makefile✔   Write the tests✔   Write the code    Write the d...
Where are We?✔   Stub source, test, and doc files✔   Create the Makefile✔   Write the tests✔   Write the code✔   Write the d...
Where are We?✔   Stub source, test, and doc files✔   Create the Makefile✔   Write the tests✔   Write the code✔   Write the d...
Where are We?✔   Stub source, test, and doc files✔   Create the Makefile✔   Write the tests✔   Write the code✔   Write the d...
Tom Lanetgl@postgresql.orghttp://postgresql.org/~tgl/tomlane@tomlaneI’ve got some killer extensions in development that I ...
Tom Lanetgl@postgresql.orghttp://postgresql.org/~tgl/tomlane@tomlaneI’ve got some killer extensions in development that I ...
tomlane
omg WTF ROTFL lolzomg WTF ROTFL lolz
omg WTF ROTFL lolzomg WTF ROTFL lolz
tomlane●●●●●●●●●●●●●●●●●●
tomlane
tomlane
META.json
{


"name":
"pair",


"abstract":
"A
key/value
pair
data
type",


"description":
"This
library
contains
a
single
PostgreSQ...
{


"name":
"pair",


"abstract":
"A
key/value
pair
data
type",


"description":
"This
library
contains
a
single
PostgreSQ...
{


"name":
"pair",


"abstract":
"A
key/value
pair
data
type",


"description":
"This
library
contains
a
single
PostgreSQ...
{


"name":
"pair",


"abstract":
"A
key/value
pair
data
type",


"description":
"This
library
contains
a
single
PostgreSQ...
{


"name":
"pair",


"abstract":
"A
key/value
pair
data
type",


"description":
"This
library
contains
a
single
PostgreSQ...
Where are We?✔   Stub source, test, and doc files✔   Create the Makefile✔   Write the tests✔   Write the code✔   Write the d...
Where are We?✔   Stub source, test, and doc files✔   Create the Makefile✔   Write the tests✔   Write the code✔   Write the d...
Why PGXN?
Why PGXN?World-wide network of mirrors
Why PGXN?World-wide network of mirrorsFully indexed extensions
Why PGXN?World-wide network of mirrorsFully indexed extensionsRESTful metadata distribution
Why PGXN?World-wide network of mirrorsFully indexed extensionsRESTful metadata distributionSearch and documentation site
Why PGXN?World-wide network of mirrorsFully indexed extensionsRESTful metadata distributionSearch and documentation siteCo...
Why PGXN?World-wide network of mirrorsFully indexed extensionsRESTful metadata distributionSearch and documentation siteCo...
pair
pair
PGXN Client%
PGXN Client%

sudo
easy_install
pgxnclientInstalling
pgxncli.py
script
to
/usr/local/binInstalling
pgxn
script
to
/usr/loc...
PGXN Client%

sudo
easy_install
pgxnclientInstalling
pgxncli.py
script
to
/usr/local/binInstalling
pgxn
script
to
/usr/loc...
PGXN Client%

sudo
easy_install
pgxnclientInstalling
pgxncli.py
script
to
/usr/local/binInstalling
pgxn
script
to
/usr/loc...
PGXN Client%

sudo
easy_install
pgxnclientInstalling
pgxncli.py
script
to
/usr/local/binInstalling
pgxn
script
to
/usr/loc...
Thank youDaniele Varrazzo
Coming in 9.1
Coming in 9.19.1 adding extension support
Coming in 9.19.1 adding extension supportCREATE
EXTENSION
pair
WITH
SCHEMA
utils;
Coming in 9.19.1 adding extension supportCREATE
EXTENSION
pair
WITH
SCHEMA
utils;Encapsulates dump/restore
Coming in 9.19.1 adding extension supportCREATE
EXTENSION
pair
WITH
SCHEMA
utils;Encapsulates dump/restoreWhatever is defin...
Coming in 9.19.1 adding extension supportCREATE
EXTENSION
pair
WITH
SCHEMA
utils;Encapsulates dump/restoreWhatever is defin...
9.1 Extension Packaging
9.1 Extension Packaging Control file
9.1 Extension Packaging Control file Migration from unpackaged
9.1 Extension Packaging Control file Migration from unpackaged   pair--unpackaged--0.1.0.sql
9.1 Extension Packaging Control file Migration from unpackaged   pair--unpackaged--0.1.0.sql Properly-named SQL script
9.1 Extension Packaging Control file Migration from unpackaged   pair--unpackaged--0.1.0.sql Properly-named SQL script   pa...
Create the control filepair.control
Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir...
Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir...
Create the control file                         Optional#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version...
Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir...
Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir...
Create the control file#
pair
extension                                 For Ccomment
=
A
key/value
pair
data
typedefault_ve...
Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir...
Create the control file#
pair
extension Can movecomment
=
A
key/value
pair
data
type                 schemasdefault_version...
Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir...
Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir...
Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir...
Migration from                    Unpackagedsql/pair‐‐unpac…
Migration from                      UnpackagedALTER
EXTENSION
pair
ADD
TYPE
pair;ALTER
EXTENSION
pair
ADD
FUNCTION
pair(an...
Migration from                      UnpackagedALTER
EXTENSION
pair
ADD
TYPE
pair;ALTER
EXTENSION
pair
ADD
FUNCTION
pair(an...
Update the MakefileMakefile
Update the MakefileEXTENSION



=
pairEXTVERSION


=
$(shell
grep
default_version


$(EXTENSION).control
|

sed
‐e


"s/def...
Update the MakefileEXTENSION



=
pairEXTVERSION


=
$(shell
grep
default_version


$(EXTENSION).control
|

sed
‐e


"s/def...
Update the MakefileEXTENSION



=
pairEXTVERSION


=
$(shell
grep
default_version


$(EXTENSION).control
|

sed
‐e


"s/def...
Update the Makefile                                               Extract fromEXTENSION



=
pairEXTVERSION


=
$(shell
gre...
Update the MakefileEXTENSION



=
pairEXTVERSION


=
$(shell
grep
default_version


$(EXTENSION).control
|

sed
‐e


"s/def...
Update the MakefileEXTENSION



=
pairEXTVERSION


=
$(shell
grep
default_version


$(EXTENSION).control
|

sed
‐e


"s/def...
Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes) Makefile
Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)...
Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)...
Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)...
Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)...
Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)...
Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)...
Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)...
Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)...
Or Forget It
Or Forget ItCopy Makefile
Or Forget ItCopy MakefileEdit first line
Or Forget ItCopy MakefileEdit first line   EXTENSION=pair
Or Forget ItCopy MakefileEdit first line   EXTENSION=pairIgnore the rest
Or Forget ItCopy MakefileEdit first line   EXTENSION=pairIgnore the rest             Better still…
Skeleton in the Closet%
Skeleton in the Closet%

sudo
gem
install
pgxn_utilsInstalling
ri
documentation
for
pgxn_utils‐0.1.2...Installing
RDoc
doc...
Skeleton in the Closet%

sudo
gem
install
pgxn_utilsInstalling
ri
documentation
for
pgxn_utils‐0.1.2...Installing
RDoc
doc...
Thank youDickson S. Guedes
Install Extension%
Install Extension

pgxn
load
pair
‐d
try%INFO:
best
version:
pair
0.1.2CREATE
EXTENSION%    Nice.
Resources
Resourceshttp:/     /pgxn.org/ — Browse
Resourceshttp:/     /pgxn.org/ — Browsehttp:/     /manager.pgxn.org/ — Release
Resourceshttp:/     /pgxn.org/ — Browsehttp:/     /manager.pgxn.org/ — Releasehttp:/     /s.coop/pgextdocs — Learn
Resourceshttp:/     /pgxn.org/ — Browsehttp:/     /manager.pgxn.org/ — Releasehttp:/     /s.coop/pgextdocs — Learnhttp:/  ...
Resourceshttp:/     /pgxn.org/ — Browsehttp:/     /manager.pgxn.org/ — Releasehttp:/     /s.coop/pgextdocs — Learnhttp:/  ...
Resourceshttp:/     /pgxn.org/ — Browsehttp:/     /manager.pgxn.org/ — Releasehttp:/     /s.coop/pgextdocs — Learnhttp:/  ...
Building and Distributing     PostgreSQL Extensions      Without Learning C                                               ...
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Building and Distributing PostgreSQL Extensions Without Learning C
Upcoming SlideShare
Loading in …5
×

Building and Distributing PostgreSQL Extensions Without Learning C

545 views

Published on

An introduction to developing, packaging, and distributing extensions to the PostgreSQL RDBMS without learning C and with maximum audience exposure.

Published in: Technology, Education
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
545
On SlideShare
0
From Embeds
0
Number of Embeds
3
Actions
Shares
0
Downloads
18
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide
  • TODO:\n• Add more privilege stuff?\n• Add example of renaming `flips.timestamp`?\n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • Building and Distributing PostgreSQL Extensions Without Learning C

    1. 1. Building and Distributing PostgreSQL Extensions Without Learning C David E. Wheeler PostgreSQL Experts, Inc. PGXPUG PgDay 2011Text: Attribution-Noncommercial-Share Alike 3.0 United States:http://creativecommons.org/licenses/by-nc-sa/3.0/us/Images licensed independently and © Their respective owners.
    2. 2. There arequite a fewPostgreSQLExtensions.
    3. 3. Core Extensions
    4. 4. Core Extensionsintagg pgcryptointarray btree_ginhstore chkpasscitext dblinkcube earthdistanceisn fuzzystrmatch
    5. 5. Common Extensions
    6. 6. Common ExtensionsPostGIS PL/Rtemporal OracleFCEpgmemcache mysqlcompatPL/Proxy pg_randomnessPL/Java pgTAPPL/Ruby
    7. 7. You can createextensions, too!
    8. 8. I know whatyou’rethinking…
    9. 9. “Sure, but I get C sick.”
    10. 10. Fortunately…
    11. 11. There aremany waysto extendPostgreSQL.
    12. 12. Functionstruth.sql
    13. 13. FunctionsCREATE
FUNCTION
truthy(



text)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS
$$



SELECT
$1
IS
NOT
NULL






AND
$1
NOT
IN
(,
0)






AND
NOT
$1
~
^[0.]+$;$$; truth.sql
    14. 14. FunctionsCREATE
FUNCTION
truthy(



text)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS
$$



SELECT
$1
IS
NOT
NULL






AND
$1
NOT
IN
(,
0)






AND
NOT
$1
~
^[0.]+$;$$; truth.sql
    15. 15. FunctionsCREATE
FUNCTION
truthy(



text)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS
$$



SELECT
$1
IS
NOT
NULL






AND
$1
NOT
IN
(,
0)






AND
NOT
$1
~
^[0.]+$;$$; truth.sql
    16. 16. FunctionsCREATE
FUNCTION
truthy(



text)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS
$$



SELECT
$1
IS
NOT
NULL






AND
$1
NOT
IN
(,
0)






AND
NOT
$1
~
^[0.]+$;$$; truth.sql
    17. 17. FunctionsCREATE
FUNCTION
truthy(



text)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS
$$



SELECT
$1
IS
NOT
NULL






AND
$1
NOT
IN
(,
0)






AND
NOT
$1
~
^[0.]+$;$$; truth.sql
    18. 18. FunctionsCREATE
FUNCTION
truthy(



text)
RETURNS
BOOLEAN
IMMUTABLE
LANGUAGE
SQL
AS
$$



SELECT
$1
IS
NOT
NULL






AND
$1
NOT
IN
(,
0)






AND
NOT
$1
~
^[0.]+$;$$; truth.sql Might be handy!
    19. 19. Domainstimezone.sql
    20. 20. DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM
NOW()
AT
TIME
ZONE
tz;



RETURN
TRUE;EXCEPTION
WHEN
invalid_parameter_value
THEN



RETURN
FALSE;END;$$;CREATE
DOMAIN
timezone
AS
CITEXT
CHECK
(
is_timezone(
VALUE
)
); timezone.sql
    21. 21. DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM
NOW()
AT
TIME
ZONE
tz;



RETURN
TRUE;EXCEPTION
WHEN
invalid_parameter_value
THEN



RETURN
FALSE;END;$$;CREATE
DOMAIN
timezone
AS
CITEXT
CHECK
(
is_timezone(
VALUE
)
); timezone.sql
    22. 22. DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM
NOW()
AT
TIME
ZONE
tz;



RETURN
TRUE;EXCEPTION
WHEN
invalid_parameter_value
THEN



RETURN
FALSE;END;$$;CREATE
DOMAIN
timezone
AS
CITEXT
CHECK
(
is_timezone(
VALUE
)
); timezone.sql
    23. 23. DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM
NOW()
AT
TIME
ZONE
tz;



RETURN
TRUE;EXCEPTION
WHEN
invalid_parameter_value
THEN



RETURN
FALSE;END;$$;CREATE
DOMAIN
timezone
AS
CITEXT
CHECK
(
is_timezone(
VALUE
)
); timezone.sql
    24. 24. DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM
NOW()
AT
TIME
ZONE
tz;



RETURN
TRUE;EXCEPTION
WHEN
invalid_parameter_value
THEN



RETURN
FALSE;END;$$;CREATE
DOMAIN
timezone
AS
CITEXT
CHECK
(
is_timezone(
VALUE
)
); timezone.sql
    25. 25. DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM
NOW()
AT
TIME
ZONE
tz;



RETURN
TRUE;EXCEPTION
WHEN
invalid_parameter_value
THEN



RETURN
FALSE;END;$$;CREATE
DOMAIN
timezone
AS
CITEXT
CHECK
(
is_timezone(
VALUE
)
); timezone.sql
    26. 26. DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM
NOW()
AT
TIME
ZONE
tz;



RETURN
TRUE;EXCEPTION
WHEN
invalid_parameter_value
THEN



RETURN
FALSE;END;$$;CREATE
DOMAIN
timezone
AS
CITEXT
CHECK
(
is_timezone(
VALUE
)
); timezone.sql
    27. 27. DomainsCREATE
OR
REPLACE
FUNCTION
is_timezone(



tz
CITEXT)
RETURNS
BOOLEAN
LANGUAGE
plpgsql
STABLE
AS
$$BEGIN



PERFORM
NOW()
AT
TIME
ZONE
tz;



RETURN
TRUE;EXCEPTION
WHEN
invalid_parameter_value
THEN



RETURN
FALSE;END;$$; I use thisCREATE
DOMAIN
timezone
AS
CITEXT
CHECK
(
is_timezone(
VALUE
)
); timezone.sql
    28. 28. Typespair.sql
    29. 29. TypesCREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
store(



params
variadic
pair[])
RETURNS
VOID
LANGUAGE
plpgsql
AS
$$DECLARE



param
pair;BEGIN



FOR
param
IN
SELECT
*
FROM
unnest(params)
LOOP







‐‐
...



END
LOOP;END;$$ pair.sql
    30. 30. TypesCREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
store(



params
variadic
pair[])
RETURNS
VOID
LANGUAGE
plpgsql
AS
$$DECLARE



param
pair;BEGIN



FOR
param
IN
SELECT
*
FROM
unnest(params)
LOOP







‐‐
...



END
LOOP;END;$$ pair.sql
    31. 31. TypesCREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
store(



params
variadic
pair[])
RETURNS
VOID
LANGUAGE
plpgsql
AS
$$DECLARE



param
pair;BEGIN



FOR
param
IN
SELECT
*
FROM
unnest(params)
LOOP







‐‐
...



END
LOOP;END;$$ pair.sql
    32. 32. TypesCREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
store(



params
variadic
pair[])
RETURNS
VOID
LANGUAGE
plpgsql
AS
$$DECLARE



param
pair;BEGIN



FOR
param
IN
SELECT
*
FROM
unnest(params)
LOOP







‐‐
...



END
LOOP;END;$$ pair.sql
    33. 33. TypesCREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
store(



params
variadic
pair[])
RETURNS
VOID
LANGUAGE
plpgsql
AS
$$DECLARE



param
pair;BEGIN



FOR
param
IN
SELECT
*
FROM
unnest(params)
LOOP







‐‐
...



END
LOOP;END;$$ pair.sql Could be useful…
    34. 34. Operatorstruthy.sql
    35. 35. OperatorsCREATE
OPERATOR
@
(
 RIGHTARG

=
text,
 PROCEDURE
=
truthy); truthy.sql
    36. 36. OperatorsCREATE
OPERATOR
@
(
 RIGHTARG

=
text,
 PROCEDURE
=
truthy); truthy.sql
    37. 37. OperatorsCREATE
OPERATOR
@
(
 RIGHTARG

=
text,
 PROCEDURE
=
truthy); truthy.sql
    38. 38. OperatorsCREATE
OPERATOR
@
(
 RIGHTARG

=
text,
 PROCEDURE
=
truthy);SELECT
@foo;
‐‐
trueSELECT
@;



‐‐
false truthy.sql
    39. 39. OperatorsCREATE
OPERATOR
@
(
 RIGHTARG

=
text,
 PROCEDURE
=
truthy);SELECT
@foo;
‐‐
trueSELECT
@;



‐‐
false truthy.sql I could use this!
    40. 40. PostgreSQLis not merelya database…
    41. 41. …It’s anapplicationdevelopmentplatform.
    42. 42. Developreusableextensionlibraries…
    43. 43. And combinethem whenbuilding apps.
    44. 44. Let’s create anextension anddistribute it.
    45. 45. The Steps
    46. 46. The StepsStub source, test, and doc files
    47. 47. The StepsStub source, test, and doc filesCreate the Makefile
    48. 48. The StepsStub source, test, and doc filesCreate the MakefileWrite the tests
    49. 49. The StepsStub source, test, and doc filesCreate the MakefileWrite the testsWrite the code
    50. 50. The StepsStub source, test, and doc filesCreate the MakefileWrite the testsWrite the codeWrite the docs
    51. 51. The StepsStub source, test, and doc filesCreate the MakefileWrite the testsWrite the codeWrite the docsCreate the metadata file
    52. 52. The StepsStub source, test, and doc filesCreate the MakefileWrite the testsWrite the codeWrite the docsCreate the metadata filePackage it up
    53. 53. The StepsStub source, test, and doc filesCreate the MakefileWrite the testsWrite the codeWrite the docsCreate the metadata filePackage it upRelease
    54. 54. Stub the Sourcesql/pair.sql
    55. 55. Stub the SourceSET
client_min_messages
=
warning;‐‐
Create
the
type
here. sql/pair.sql
    56. 56. Stub the SourceSET
client_min_messages
=
warning;‐‐
Create
the
type
here. sql/pair.sql
    57. 57. Stub the SourceSET
client_min_messages
=
warning;‐‐
Create
the
type
here. sql/pair.sql
    58. 58. Stub the SourceSET
client_min_messages
=
warning;‐‐
Create
the
type
here. sql/pair.sql sql/pair.sql
    59. 59. Stub the Teststest/sql/base.sql
    60. 60. Stub the TestsBEGIN;set
ECHO
0set
QUIET
1i
sql/pair.sqlset
QUIET
0ROLLBACK; test/sql/base.sql
    61. 61. Stub the TestsBEGIN;set
ECHO
0set
QUIET
1i
sql/pair.sqlset
QUIET
0ROLLBACK; test/sql/base.sql
    62. 62. Stub the TestsBEGIN;set
ECHO
0set
QUIET
1i
sql/pair.sqlset
QUIET
0ROLLBACK; test/sql/base.sql test/sql/base.sql
    63. 63. Stub the Docstest/sql/base.sql
    64. 64. Stub the Docspair
0.1.0==========Synopsis‐‐‐‐‐‐‐‐



‐‐
Example
code
here.



Description‐‐‐‐‐‐‐‐‐‐‐This
library
blah
blah
blah...Usage‐‐‐‐‐Heres
how
to
use
this
library.Author‐‐‐‐‐‐[David
E.
Wheeler](http://justatheory.com/)Copyright‐‐‐‐‐‐‐‐‐Copyright
(c)
2010
David
E.
Wheeler. test/sql/base.sql
    65. 65. Stub the Docspair
0.1.0 doc/pair.txt==========Synopsis‐‐‐‐‐‐‐‐



‐‐
Example
code
here.



Description‐‐‐‐‐‐‐‐‐‐‐This
library
blah
blah
blah...Usage‐‐‐‐‐Heres
how
to
use
this
library.Author‐‐‐‐‐‐[David
E.
Wheeler](http://justatheory.com/)Copyright‐‐‐‐‐‐‐‐‐Copyright
(c)
2010
David
E.
Wheeler. test/sql/base.sql
    66. 66. Create the MakefileMakefile
    67. 67. Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c)) Makefile
    68. 68. Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c)) Makefile
    69. 69. Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c)) Makefile
    70. 70. Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c)) Makefile
    71. 71. Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))TESTS
=
$(wildcard
test/sql/*.sql)REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))REGRESS_OPTS
=
‐‐inputdir=test Makefile
    72. 72. Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))TESTS
=
$(wildcard
test/sql/*.sql)REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))REGRESS_OPTS
=
‐‐inputdir=test Makefile
    73. 73. Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))TESTS
=
$(wildcard
test/sql/*.sql)REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))REGRESS_OPTS
=
‐‐inputdir=test Makefile
    74. 74. Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))TESTS
=
$(wildcard
test/sql/*.sql)REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))REGRESS_OPTS
=
‐‐inputdir=test Makefile
    75. 75. Create the Makefile inputdirDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))TESTS
=
$(wildcard
test/sql/*.sql)REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))REGRESS_OPTS
=
‐‐inputdir=test Makefile
    76. 76. Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))TESTS
=
$(wildcard
test/sql/*.sql)REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))REGRESS_OPTS
=
‐‐inputdir=testPG_CONFIG
=
pg_configPGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)include
$(PGXS) Makefile
    77. 77. Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))TESTS
=
$(wildcard
test/sql/*.sql)REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))REGRESS_OPTS
=
‐‐inputdir=testPG_CONFIG
=
pg_configPGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)include
$(PGXS) Makefile
    78. 78. Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))TESTS
=
$(wildcard
test/sql/*.sql)REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))REGRESS_OPTS
=
‐‐inputdir=testPG_CONFIG
=
pg_configPGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)include
$(PGXS) Makefile
    79. 79. Create the MakefileDATA



=
$(wildcard
sql/*.sql)DOCS



=
$(wildcard
doc/*.*)MODULES
=
$(patsubst
%.c,%,$(wildcard
src/*.c))TESTS
=
$(wildcard
test/sql/*.sql)REGRESS
=
$(patsubst
test/sql/%.sql,%,$(TESTS))REGRESS_OPTS
=
‐‐inputdir=testPG_CONFIG
=
pg_configPGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)include
$(PGXS) Makefile
    80. 80. Give it a Try%

    81. 81. Give it a Try

make%
make:
Nothing
to
be
done
for
`all.%
    82. 82. Give it a Try

make%
make:
Nothing
to
be
done
for
`all.

make
installcheck%pg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test
base===
dropping
database
"regression"

===DROP
DATABASE===
creating
database
"regression"

===CREATE
DATABASEALTER
DATABASE===
running
regression
test
queries
===test
base
















...
diff:

test/expected/base.out:
No
such
file
or
directorydiff
command
failed
with
status
512:

diff

"test/expected/base.out"
"results/base.out"
>
"results/base.out.diff"make:
***
[installcheck]
Error
2%
    83. 83. Give it a Try

make%
make:
Nothing
to
be
done
for
`all.

make
installcheck%pg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test
base===
dropping
database
"regression"

===DROP
DATABASE===
creating
database
"regression"

===CREATE
DATABASEALTER
DATABASE===
running
regression
test
queries
===test
base
















...
diff:

test/expected/base.out:
No
such
file
or
directorydiff
command
failed
with
status
512:

diff

"test/expected/base.out"
"results/base.out"
>
"results/base.out.diff"make:
***
[installcheck]
Error
2%
    84. 84. Give it a Try

make%
make:
Nothing
to
be
done
for
`all.

make
installcheck%pg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test
base===
dropping
database
"regression"

===DROP
DATABASE===
creating
database
"regression"

=== We’ll comeCREATE
DATABASEALTER
DATABASE back to that.===
running
regression
test
queries
===test
base
















...
diff:

test/expected/base.out:
No
such
file
or
directorydiff
command
failed
with
status
512:

diff

"test/expected/base.out"
"results/base.out"
>
"results/base.out.diff"make:
***
[installcheck]
Error
2%
    85. 85. Write Some Testsi
sql/pair.sqlset
QUIET
0ROLLBACK; test/sql/base.sql
    86. 86. Write Some Testsi
sql/pair.sqlset
QUIET
0





SELECT
pair(foo,
bar)UNION
SELECT
pair(HEY::text,
bar)UNION
SELECT
pair(foo::text,
woah::text)UNION
SELECT
pair(ick,
foo::text)UNION
SELECT
pair(foo::text,
1)UNION
SELECT
pair(12.3,
foo::text)UNION
SELECT
pair(1,
12)ORDER
BY
pair;ROLLBACK; test/sql/base.sql
    87. 87. Write Some Testsi
sql/pair.sqlset
QUIET
0





SELECT
pair(foo,
bar)UNION
SELECT
pair(HEY::text,
bar)UNION
SELECT
pair(foo::text,
woah::text)UNION
SELECT
pair(ick,
foo::text)UNION
SELECT
pair(foo::text,
1)UNION
SELECT
pair(12.3,
foo::text)UNION
SELECT
pair(1,
12)ORDER
BY
pair;ROLLBACK; test/sql/base.sql
    88. 88. Write Some Testsi
sql/pair.sqlset
QUIET
0





SELECT
pair(foo,
bar)UNION
SELECT
pair(HEY::text,
bar)UNION
SELECT
pair(foo::text,
woah::text)UNION
SELECT
pair(ick,
foo::text)UNION
SELECT
pair(foo::text,
1)UNION
SELECT
pair(12.3,
foo::text)UNION
SELECT
pair(1,
12)ORDER
BY
pair;ROLLBACK; test/sql/base.sql
    89. 89. Write Some Testsi
sql/pair.sqlset
QUIET
0





SELECT
pair(foo,
bar)UNION
SELECT
pair(HEY::text,
bar)UNION
SELECT
pair(foo::text,
woah::text)UNION
SELECT
pair(ick,
foo::text)UNION
SELECT
pair(foo::text,
1)UNION
SELECT
pair(12.3,
foo::text)UNION
SELECT
pair(1,
12)ORDER
BY
pair;ROLLBACK; test/sql/base.sql
    90. 90. Run Them%

    91. 91. Run Them%


psql
‐d
try
‐Xf
test/sql/base.sqlBEGINpsql:test/sql/base.sql:15:
ERROR:

function
pair(unknown,
unknown)
does
not
existLINE
1:
SELECT
pair(foo,
bar)














^ROLLBACK%
    92. 92. Run Them%


psql
‐d
try
‐Xf
test/sql/base.sqlBEGINpsql:test/sql/base.sql:15:
ERROR:

function
pair(unknown,
unknown)
does
not
existLINE
1:
SELECT
pair(foo,
bar)














^ROLLBACK% Guess we should write it.
    93. 93. SET
client_min_messages
=
warning;‐‐
Create
the
type
here. sql/pair.sql
    94. 94. SET
client_min_messages
=
warning;CREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(text,
anyelement)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
anyelement)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;; sql/pair.sql
    95. 95. SET
client_min_messages
=
warning;CREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(text,
anyelement)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
anyelement)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;; sql/pair.sql
    96. 96. SET
client_min_messages
=
warning;CREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(text,
anyelement)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
anyelement)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;; sql/pair.sql
    97. 97. SET
client_min_messages
=
warning;CREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(text,
anyelement)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
anyelement)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;; sql/pair.sql
    98. 98. SET
client_min_messages
=
warning;CREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(text,
anyelement)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
anyelement)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;; sql/pair.sql
    99. 99. SET
client_min_messages
=
warning;CREATE
TYPE
pair
AS
(
k
text,
v
text
);CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(text,
anyelement)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(anyelement,
anyelement)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;; sql/pair.sql
    100. 100. %
    101. 101. %

psql
‐d
try
‐Xf
test/sql/base.sqlBEGIN



pair



‐‐‐‐‐‐‐‐‐‐‐‐
(1,12)
(12.3,foo)
(HEY,bar)
(foo,1)
(foo,bar)
(foo,woah)
(ick,foo)(7
rows)ROLLBACK% W00t!
    102. 102. Moar Tests!UNION
SELECT
pair(12.3,
foo::text)UNION
SELECT
pair(1,
12)ORDER
BY
pair;ROLLBACK; test/sql/base.sql
    103. 103. Moar Tests!UNION
SELECT
pair(12.3,
foo::text)UNION
SELECT
pair(1,
12)ORDER
BY
pair;





SELECT
foo
~>
bar
AS
arrowopUNION
SELECT
HEY::text
~>
barUNION
SELECT
foo::text
~>
woah::textUNION
SELECT
ick
~>
foo::textUNION
SELECT
foo::text
~>
1UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;ROLLBACK; test/sql/base.sql
    104. 104. Moar Tests!UNION
SELECT
pair(12.3,
foo::text)UNION
SELECT
pair(1,
12)ORDER
BY
pair;





SELECT
foo
~>
bar
AS
arrowopUNION
SELECT
HEY::text
~>
barUNION
SELECT
foo::text
~>
woah::textUNION
SELECT
ick
~>
foo::textUNION
SELECT
foo::text
~>
1UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;ROLLBACK; test/sql/base.sql
    105. 105. Moar Tests!UNION
SELECT
pair(12.3,
foo::text)UNION
SELECT
pair(1,
12)ORDER
BY
pair;





SELECT
foo
~>
bar
AS
arrowopUNION
SELECT
HEY::text
~>
barUNION
SELECT
foo::text
~>
woah::textUNION
SELECT
ick
~>
foo::textUNION
SELECT
foo::text
~>
1UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;ROLLBACK; test/sql/base.sql
    106. 106. Moar Tests!UNION
SELECT
pair(12.3,
foo::text)UNION
SELECT
pair(1,
12)ORDER
BY
pair;





SELECT
foo
~>
bar
AS
arrowopUNION
SELECT
HEY::text
~>
barUNION
SELECT
foo::text
~>
woah::textUNION
SELECT
ick
~>
foo::textUNION
SELECT
foo::text
~>
1UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;ROLLBACK; test/sql/base.sql
    107. 107. %
    108. 108. %

psql
‐d
try
‐Xf
test/sql/base.sqlBEGIN



pair



‐‐‐‐‐‐‐‐‐‐‐‐
(1,12)
(12.3,foo)
(HEY,bar)
(foo,1)
(foo,bar)
(foo,woah)
(ick,foo)(7
rows)psql:test/sql/base.sql:25:
ERROR:

operator
does
not
exist:
unknown
~>
unknownLINE
1:
SELECT
foo
~>
bar
AS
arrowop




















^HINT:

No
operator
matches
the
given
name
and
argument
type(s).
You
might
need
to
add
explicit
type
casts.ROLLBACK%
    109. 109. 
(foo,1)
(foo,bar)%
(foo,woah)
(ick,foo)(7
rows)psql:test/sql/base.sql:25:
ERROR:

operator
unknown
~>
unknownLINE
1:
SELECT
foo
~>
bar
AS
arrowop




















^ Guess weHINT:

No
operator
matches
the
given
name
an should write it.type(s).
You
might
need
to
add
explicit
typeROLLBACK%
    110. 110. CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;; sql/pair.sql
    111. 111. CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;;CREATE
OPERATOR
~>
(
 LEFTARG


=
text,
 RIGHTARG

=
anyelement,
 PROCEDURE
=
pair);CREATE
OPERATOR
~>
(
 LEFTARG


=
anyelement,
 RIGHTARG

=
text,
 PROCEDURE
=
pair);CREATE
OPERATOR
~>
(
 LEFTARG


=
anyelement,
 RIGHTARG

=
anyelement,
 PROCEDURE
=
pair);CREATE
OPERATOR
~>
(
 LEFTARG


=
text,
 RIGHTARG

=
text,
 PROCEDURE
=
pair); sql/pair.sql
    112. 112. CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;;CREATE
OPERATOR
~>
(
 LEFTARG


=
text,
 RIGHTARG

=
anyelement,
 PROCEDURE
=
pair);CREATE
OPERATOR
~>
(
 LEFTARG


=
anyelement,
 RIGHTARG

=
text,
 PROCEDURE
=
pair);CREATE
OPERATOR
~>
(
 LEFTARG


=
anyelement,
 RIGHTARG

=
anyelement,
 PROCEDURE
=
pair);CREATE
OPERATOR
~>
(
 LEFTARG


=
text,
 RIGHTARG

=
text,
 PROCEDURE
=
pair); sql/pair.sql
    113. 113. CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;;CREATE
OPERATOR
~>
(
 LEFTARG


=
text,
 RIGHTARG

=
anyelement,
 PROCEDURE
=
pair);CREATE
OPERATOR
~>
(
 LEFTARG


=
anyelement,
 RIGHTARG

=
text,
 PROCEDURE
=
pair);CREATE
OPERATOR
~>
(
 LEFTARG


=
anyelement,
 RIGHTARG

=
anyelement,
 PROCEDURE
=
pair);CREATE
OPERATOR
~>
(
 LEFTARG


=
text,
 RIGHTARG

=
text,
 PROCEDURE
=
pair); sql/pair.sql
    114. 114. CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;;CREATE
OPERATOR
~>
(
 LEFTARG


=
text,
 RIGHTARG

=
anyelement,
 PROCEDURE
=
pair);CREATE
OPERATOR
~>
(
 LEFTARG


=
anyelement,
 RIGHTARG

=
text,
 PROCEDURE
=
pair);CREATE
OPERATOR
~>
(
 LEFTARG


=
anyelement,
 RIGHTARG

=
anyelement,
 PROCEDURE
=
pair);CREATE
OPERATOR
~>
(
 LEFTARG


=
text,
 RIGHTARG

=
text,
 PROCEDURE
=
pair); sql/pair.sql
    115. 115. CREATE
OR
REPLACE
FUNCTION
pair(text,
text)RETURNS
pair
LANGUAGE
SQL
AS
SELECT
ROW($1,
$2)::pair;;CREATE
OPERATOR
~>
(
 LEFTARG


=
text,
 RIGHTARG

=
anyelement,
 PROCEDURE
=
pair);CREATE
OPERATOR
~>
(
 LEFTARG


=
anyelement,
 RIGHTARG

=
text,
 PROCEDURE
=
pair);CREATE
OPERATOR
~>
(
 LEFTARG


=
anyelement,
 RIGHTARG

=
anyelement,
 PROCEDURE
=
pair);CREATE
OPERATOR
~>
(
 LEFTARG


=
text,
 RIGHTARG

=
text,
 PROCEDURE
=
pair); sql/pair.sql
    116. 116. %
    117. 117. %

psql
‐d
try
‐Xf
test/sql/base.sqlBEGIN



pair



‐‐‐‐‐‐‐‐‐‐‐‐
(1,12)
(12.3,foo)
(HEY,bar)
(foo,1)
(foo,bar)
(foo,woah)
(ick,foo)(7
rows)

arrowop


‐‐‐‐‐‐‐‐‐‐‐‐
(1,12)
(12.3,foo)
(HEY,bar)
(foo,1)
(foo,bar)
(foo,woah)
(ick,foo)(7
rows)ROLLBACK
    118. 118. 
(foo,bar)
(foo,woah)%
(ick,foo)(7
rows)

arrowop


‐‐‐‐‐‐‐‐‐‐‐‐
(1,12)
(12.3,foo)
(HEY,bar)
(foo,1)
(foo,bar)
(foo,woah)
(ick,foo)(7
rows) Yay!ROLLBACK
    119. 119. One More Time!UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;ROLLBACK; test/sql/base.sql
    120. 120. One More Time!UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;CREATE
TABLE
kv
(



pair
pair);INSERT
INTO
kv
VALUES(foo
~>
bar),
(1
~>
2);SELECT
(pair).k,
(pair).v
FROM
kv
ORDER
BY
(pair).k;SELECT
(pair(foo,
bar)).k,
(pair(foo,
bar)).v;SELECT
(foo
~>
bar).k,
(foo
~>
bar).v;ROLLBACK; test/sql/base.sql
    121. 121. One More Time!UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;CREATE
TABLE
kv
(



pair
pair);INSERT
INTO
kv
VALUES(foo
~>
bar),
(1
~>
2);SELECT
(pair).k,
(pair).v
FROM
kv
ORDER
BY
(pair).k;SELECT
(pair(foo,
bar)).k,
(pair(foo,
bar)).v;SELECT
(foo
~>
bar).k,
(foo
~>
bar).v;ROLLBACK; test/sql/base.sql
    122. 122. One More Time!UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;CREATE
TABLE
kv
(



pair
pair);INSERT
INTO
kv
VALUES(foo
~>
bar),
(1
~>
2);SELECT
(pair).k,
(pair).v
FROM
kv
ORDER
BY
(pair).k;SELECT
(pair(foo,
bar)).k,
(pair(foo,
bar)).v;SELECT
(foo
~>
bar).k,
(foo
~>
bar).v;ROLLBACK; test/sql/base.sql
    123. 123. One More Time!UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;CREATE
TABLE
kv
(



pair
pair);INSERT
INTO
kv
VALUES(foo
~>
bar),
(1
~>
2);SELECT
(pair).k,
(pair).v
FROM
kv
ORDER
BY
(pair).k;SELECT
(pair(foo,
bar)).k,
(pair(foo,
bar)).v;SELECT
(foo
~>
bar).k,
(foo
~>
bar).v;ROLLBACK; test/sql/base.sql
    124. 124. One More Time!UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;CREATE
TABLE
kv
(



pair
pair);INSERT
INTO
kv
VALUES(foo
~>
bar),
(1
~>
2);SELECT
(pair).k,
(pair).v
FROM
kv
ORDER
BY
(pair).k;SELECT
(pair(foo,
bar)).k,
(pair(foo,
bar)).v;SELECT
(foo
~>
bar).k,
(foo
~>
bar).v;ROLLBACK; test/sql/base.sql
    125. 125. One More Time!UNION
SELECT
12.3
~>
foo::textUNION
SELECT
1
~>
12ORDER
BY
arrowop;CREATE
TABLE
kv
(



pair
pair);INSERT
INTO
kv
VALUES(foo
~>
bar),
(1
~>
2);SELECT
(pair).k,
(pair).v
FROM
kv
ORDER
BY
(pair).k;SELECT
(pair(foo,
bar)).k,
(pair(foo,
bar)).v;SELECT
(foo
~>
bar).k,
(foo
~>
bar).v;ROLLBACK; test/sql/base.sql
    126. 126. %
    127. 127. %

psql
‐d
try
‐Xf
test/sql/base.sqlBEGIN



pair



‐‐‐‐‐‐‐‐‐‐‐‐
(1,12)
(12.3,foo)
(HEY,bar)
(foo,1)
(foo,bar)
(foo,woah)
(ick,foo)(7
rows)

arrowop


‐‐‐‐‐‐‐‐‐‐‐‐
(1,12)
(12.3,foo)
(HEY,bar)
(foo,1)
(foo,bar)
(foo,woah)
(ick,foo)(7
rows)CREATE
TABLEINSERT
0
2

k

|

v

‐‐‐‐‐+‐‐‐‐‐
1


|
2
foo
|
bar(2
rows)

k

|

v

‐‐‐‐‐+‐‐‐‐‐
foo
|
bar We’re golden!(1
row)

k

|

v

‐‐‐‐‐+‐‐‐‐‐
foo
|
bar(1
row)ROLLBACK%
    128. 128. Remember This?%

    129. 129. Remember This?%


make
installcheckpg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test
base===
dropping
database
"regression"

===DROP
DATABASE===
creating
database
"regression"

===CREATE
DATABASEALTER
DATABASE===
running
regression
test
queries
===test
base
















...
diff:

test/expected/base.out:
No
such
file
or
directorydiff
command
failed
with
status
512:

diff

"test/expected/base.out"
"results/base.out"
>
"results/base.out.diff"make:
***
[installcheck]
Error
2%
    130. 130. Let’s Fix That%

    131. 131. Let’s Fix That%


echo
set
ECHO
0
>
test/expected/base.out
%
    132. 132. Let’s Fix That%


echo
set
ECHO
0
>
test/expected/base.out


psql
‐d
try
‐Xf
test/sql/base.sql
%

|
grep
‐v
^BEGIN
>>
test/expected/base.out%
    133. 133. Let’s Fix That%


echo
set
ECHO
0
>
test/expected/base.out


psql
‐d
try
‐Xf
test/sql/base.sql
%

|
grep
‐v
^BEGIN
>>
test/expected/base.out
%
make
installcheckpg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test
base(using
postmaster
on
Unix
socket,
default
port)==============
dropping
database
"regression"








==============DROP
DATABASE==============
creating
database
"regression"








==============CREATE
DATABASEALTER
DATABASE==============
running
regression
test
queries







==============test
base
















...
ok=====================
All
1
tests
passed.
=====================
    134. 134. Let’s Fix That%


echo
set
ECHO
0
>
test/expected/base.out


psql
‐d
try
‐Xf
test/sql/base.sql
%

|
grep
‐v
^BEGIN
>>
test/expected/base.out
%
make
installcheckpg_regress
‐‐psqldir=/pgsql/bin
‐‐inputdir=test
base(using
postmaster
on
Unix
socket,
default
port)==============
dropping
database
"regression"








==============DROP
DATABASE==============
creating
database
"regression"








==============CREATE
DATABASEALTER
DATABASE==============
running
regression
test
queries







==============test
base
















...
ok=====================
All
1
tests
passed.
===================== Woo hoo!
    135. 135. Where are We?Stub source, test, and doc filesCreate the MakefileWrite the testsWrite the codeWrite the docsCreate the metadata filePackage it upRelease
    136. 136. Where are We?✔ Stub source, test, and doc files Create the Makefile Write the tests Write the code Write the docs Create the metadata file Package it up Release
    137. 137. Where are We?✔ Stub source, test, and doc files✔ Create the Makefile Write the tests Write the code Write the docs Create the metadata file Package it up Release
    138. 138. Where are We?✔ Stub source, test, and doc files✔ Create the Makefile✔ Write the tests Write the code Write the docs Create the metadata file Package it up Release
    139. 139. Where are We?✔ Stub source, test, and doc files✔ Create the Makefile✔ Write the tests✔ Write the code Write the docs Create the metadata file Package it up Release
    140. 140. Write the Docspair
0.1.0==========Synopsis‐‐‐‐‐‐‐‐



‐‐
Example
code
here.Description‐‐‐‐‐‐‐‐‐‐‐This
library
blah
blah
blah...Usage‐‐‐‐‐Heres
how
to
use
this
library.Author‐‐‐‐‐‐[David
E.
Wheeler](http://justatheory.com/)Copyright‐‐‐‐‐‐‐‐‐Copyright
(c)
2010
David
E.
Wheeler. test/sql/base.sql
    141. 141. Write the Docspair
0.1.0==========Synopsis‐‐‐‐‐‐‐‐



%
CREATE
EXTENSION
pair;



CREATE
EXTENSION



%
SELECT
pair(foo,
bar);







pair



‐‐‐‐‐‐‐‐‐‐‐‐




(foo,bar)



%
SELECT
foo
~>
bar;







pair



‐‐‐‐‐‐‐‐‐‐‐‐




(foo,bar)Description‐‐‐‐‐‐‐‐‐‐‐This
library
blah
blah
blah...Usage‐‐‐‐‐Heres
how
to
use
this
library.Author test/sql/base.sql‐‐‐‐‐‐
    142. 142. Write the DocsDescription‐‐‐‐‐‐‐‐‐‐‐This
library
blah
blah
blah...Usage‐‐‐‐‐Heres
how
to
use
this
library.Author‐‐‐‐‐‐[David
E.
Wheeler](http://justatheory.com/)Copyright‐‐‐‐‐‐‐‐‐Copyright
(c)
2010
David
E.
Wheeler. test/sql/base.sql
    143. 143. Write the DocsDescription‐‐‐‐‐‐‐‐‐‐‐This
library
contains
a
single
PostgreSQL
extension,
a
key/value
pair
datatype
called
`pair`,
along
with
a
convenience
function
for
constructingkey/value
pairs.
Its
just
a
simple
thing,
really:
a
two‐value
composite
typethat
can
store
any
type
of
value
in
its
slots,
which
are
named
`k`
and
`v`.Usage‐‐‐‐‐Heres
how
to
use
this
library.Author‐‐‐‐‐‐[David
E.
Wheeler](http://justatheory.com/)Copyright‐‐‐‐‐‐‐‐‐Copyright
(c)
2010
David
E.
Wheeler. test/sql/base.sql
    144. 144. META.jsonMETA.json
    145. 145. META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
"Tom
Lane
<tgl@postgresql.org>",


"license":



"postgresql",


"provides":
{





"pair":
{








"file":




"pair.sql",








"version":

"0.1.0"





}


},


"meta‐spec":

{





"version":
"1.0.0",





"url":
"http://pgxn.org/meta/spec.txt"


}} META.json
    146. 146. META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
"Tom
Lane
<tgl@postgresql.org>",


"license":



"postgresql",


"provides":
{





"pair":
{








"file":




"pair.sql",








"version":

"0.1.0"





}


},


"meta‐spec":

{





"version":
"1.0.0",





"url":
"http://pgxn.org/meta/spec.txt"


}} META.json
    147. 147. META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
"Tom
Lane
<tgl@postgresql.org>",


"license":



"postgresql",


"provides":
{





"pair":
{








"file":




"pair.sql",








"version":

"0.1.0"





}


},


"meta‐spec":

{





"version":
"1.0.0",





"url":
"http://pgxn.org/meta/spec.txt"


}} META.json
    148. 148. META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
"Tom
Lane
<tgl@postgresql.org>",


"license":



"postgresql",


"provides":
{





"pair":
{








"file":




"pair.sql",








"version":

"0.1.0"





}


},


"meta‐spec":

{





"version":
"1.0.0",





"url":
"http://pgxn.org/meta/spec.txt"


}} META.json
    149. 149. META.json{ semver.org


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
"Tom
Lane
<tgl@postgresql.org>",


"license":



"postgresql",


"provides":
{





"pair":
{








"file":




"pair.sql",








"version":

"0.1.0"





}


},


"meta‐spec":

{





"version":
"1.0.0",





"url":
"http://pgxn.org/meta/spec.txt"


}} META.json
    150. 150. META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
"Tom
Lane
<tgl@postgresql.org>",


"license":



"postgresql",


"provides":
{





"pair":
{








"file":




"pair.sql",








"version":

"0.1.0"





}


},


"meta‐spec":

{





"version":
"1.0.0",





"url":
"http://pgxn.org/meta/spec.txt"


}} META.json
    151. 151. META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
"Tom
Lane
<tgl@postgresql.org>",


"license":



"postgresql",


"provides":
{





"pair":
{








"file":




"pair.sql",








"version":

"0.1.0"





}


},


"meta‐spec":

{





"version":
"1.0.0",





"url":
"http://pgxn.org/meta/spec.txt"


}} META.json
    152. 152. META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
"Tom
Lane
<tgl@postgresql.org>",


"license":



"postgresql",


"provides":
{





"pair":
{








"file":




"pair.sql",








"version":

"0.1.0"





}


},


"meta‐spec":

{





"version":
"1.0.0",





"url":
"http://pgxn.org/meta/spec.txt"


}} META.json
    153. 153. META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
"Tom
Lane
<tgl@postgresql.org>",


"license":



"postgresql",


"provides":
{





"pair":
{








"file":




"pair.sql",








"version":

"0.1.0"





}


},


"meta‐spec":

{





"version":
"1.0.0",





"url":
"http://pgxn.org/meta/spec.txt"


}} META.json
    154. 154. META.json{


"name":






"pair",


"abstract":


"A
key/value
pair
data
type",


"version":



"0.1.0",


"maintainer":
"Tom
Lane
<tgl@postgresql.org>",


"license":



"postgresql",


"provides":
{





"pair":
{








"file":




"pair.sql",








"version":

"0.1.0"





}


},


"meta‐spec":

{





"version":
"1.0.0",





"url":
"http://pgxn.org/meta/spec.txt" At least this…


}} META.json
    155. 155. Package it Up!%

    156. 156. Package it Up!%


git
archive
‐‐format
zip
‐‐prefix=pair‐0.1.0/


‐‐output
~/Desktop/pair‐0.1.0.zip
master%
    157. 157. Package it Up!%


git
archive
‐‐format
zip
‐‐prefix=pair‐0.1.0/


‐‐output
~/Desktop/pair‐0.1.0.zip
master% Easy, eh?
    158. 158. Where are We?✔ Stub source, test, and doc files✔ Create the Makefile✔ Write the tests✔ Write the code Write the docs Create the metadata file Package it up Release
    159. 159. Where are We?✔ Stub source, test, and doc files✔ Create the Makefile✔ Write the tests✔ Write the code✔ Write the docs Create the metadata file Package it up Release
    160. 160. Where are We?✔ Stub source, test, and doc files✔ Create the Makefile✔ Write the tests✔ Write the code✔ Write the docs✔ Create the metadata file Package it up Release
    161. 161. Where are We?✔ Stub source, test, and doc files✔ Create the Makefile✔ Write the tests✔ Write the code✔ Write the docs✔ Create the metadata file✔ Package it up Release
    162. 162. Tom Lanetgl@postgresql.orghttp://postgresql.org/~tgl/tomlane@tomlaneI’ve got some killer extensions in development that I thinkwill be useful to everyone, including:* pair: an ordered pair data type* PL/Brainfuck: just what it sounds like
    163. 163. Tom Lanetgl@postgresql.orghttp://postgresql.org/~tgl/tomlane@tomlaneI’ve got some killer extensions in development that I thinkwill be useful to everyone, including:* pair: an ordered pair data type* PL/Brainfuck: just what it sounds like
    164. 164. tomlane
    165. 165. omg WTF ROTFL lolzomg WTF ROTFL lolz
    166. 166. omg WTF ROTFL lolzomg WTF ROTFL lolz
    167. 167. tomlane●●●●●●●●●●●●●●●●●●
    168. 168. tomlane
    169. 169. tomlane
    170. 170. META.json
    171. 171. {


"name":
"pair",


"abstract":
"A
key/value
pair
data
type",


"description":
"This
library
contains
a
single
PostgreSQL
extension,
a
key/value


"version":
"0.1.0",


"maintainer":
"Tom
Lane
<tgl@postgresql.org>",


"license":
"postgresql",


"provides":
{





"pair":
{








"file":
"sql/pair.sql",








"version":
"0.1.0"





}


},


"resources":
{





"bugtracker":
{








"web":
"http://github.com/tgl/kv‐pair/issues/"





},





"repository":
{







"url":

"git://github.com/tgl/kv‐pair.git",







"web":

"http://github.com/tgl/kv‐pair/",







"type":
"git"





}


},


"generated_by":
"Tom
Lane",


"meta‐spec":
{





"version":
"1.0.0",





"url":
"http://pgxn.org/meta/spec.txt"


},


"tags":
[





"variadic
function",
"ordered
pair",
"pair",
"key
value",
"key
value
pair"


]} META.json
    172. 172. {


"name":
"pair",


"abstract":
"A
key/value
pair
data
type",


"description":
"This
library
contains
a
single
PostgreSQL
extension,
a
key/value


"version":
"0.1.0",


"maintainer":
"Tom
Lane
<tgl@postgresql.org>",


"license":
"postgresql",


"provides":
{





"pair":
{








"file":
"sql/pair.sql",








"version":
"0.1.0"





}


},


"resources":
{





"bugtracker":
{








"web":
"http://github.com/tgl/kv‐pair/issues/"





},





"repository":
{







"url":

"git://github.com/tgl/kv‐pair.git",







"web":

"http://github.com/tgl/kv‐pair/",







"type":
"git"





}


},


"generated_by":
"Tom
Lane",


"meta‐spec":
{





"version":
"1.0.0",





"url":
"http://pgxn.org/meta/spec.txt"


},


"tags":
[





"variadic
function",
"ordered
pair",
"pair",
"key
value",
"key
value
pair"


]} META.json
    173. 173. {


"name":
"pair",


"abstract":
"A
key/value
pair
data
type",


"description":
"This
library
contains
a
single
PostgreSQL
extension,
a
key/value


"version":
"0.1.0",


"maintainer":
"Tom
Lane
<tgl@postgresql.org>",


"license":
"postgresql",


"provides":
{





"pair":
{








"file":
"sql/pair.sql",








"version":
"0.1.0"





}


},


"resources":
{





"bugtracker":
{








"web":
"http://github.com/tgl/kv‐pair/issues/"





},





"repository":
{







"url":

"git://github.com/tgl/kv‐pair.git",







"web":

"http://github.com/tgl/kv‐pair/",







"type":
"git"





}


},


"generated_by":
"Tom
Lane",


"meta‐spec":
{





"version":
"1.0.0",





"url":
"http://pgxn.org/meta/spec.txt"


},


"tags":
[





"variadic
function",
"ordered
pair",
"pair",
"key
value",
"key
value
pair"


]} META.json
    174. 174. {


"name":
"pair",


"abstract":
"A
key/value
pair
data
type",


"description":
"This
library
contains
a
single
PostgreSQL
extension,
a
key/value


"version":
"0.1.0",


"maintainer":
"Tom
Lane
<tgl@postgresql.org>",


"license":
"postgresql",


"provides":
{





"pair":
{








"file":
"sql/pair.sql",








"version":
"0.1.0"





}


},


"resources":
{





"bugtracker":
{








"web":
"http://github.com/tgl/kv‐pair/issues/"





},





"repository":
{







"url":

"git://github.com/tgl/kv‐pair.git",







"web":

"http://github.com/tgl/kv‐pair/",







"type":
"git"





}


},


"generated_by":
"Tom
Lane",


"meta‐spec":
{





"version":
"1.0.0",





"url":
"http://pgxn.org/meta/spec.txt"


},


"tags":
[





"variadic
function",
"ordered
pair",
"pair",
"key
value",
"key
value
pair"


]} META.json
    175. 175. {


"name":
"pair",


"abstract":
"A
key/value
pair
data
type",


"description":
"This
library
contains
a
single
PostgreSQL
extension,
a
key/value


"version":
"0.1.0",


"maintainer":
"Tom
Lane
<tgl@postgresql.org>",


"license":
"postgresql",


"provides":
{





"pair":
{








"file":
"sql/pair.sql",








"version":
"0.1.0"





}


},


"resources":
{





"bugtracker":
{








"web":
"http://github.com/tgl/kv‐pair/issues/"





},





"repository":
{







"url":

"git://github.com/tgl/kv‐pair.git",







"web":

"http://github.com/tgl/kv‐pair/",







"type":
"git"





}


},


"generated_by":
"Tom
Lane",


"meta‐spec":
{





"version":
"1.0.0",





"url":
"http://pgxn.org/meta/spec.txt"


},


"tags":
[





"variadic
function",
"ordered
pair",
"pair",
"key
value",
"key
value
pair"


]} META.json
    176. 176. Where are We?✔ Stub source, test, and doc files✔ Create the Makefile✔ Write the tests✔ Write the code✔ Write the docs✔ Create the metadata file✔ Package it up Release
    177. 177. Where are We?✔ Stub source, test, and doc files✔ Create the Makefile✔ Write the tests✔ Write the code✔ Write the docs✔ Create the metadata file✔ Package it up✔ Release
    178. 178. Why PGXN?
    179. 179. Why PGXN?World-wide network of mirrors
    180. 180. Why PGXN?World-wide network of mirrorsFully indexed extensions
    181. 181. Why PGXN?World-wide network of mirrorsFully indexed extensionsRESTful metadata distribution
    182. 182. Why PGXN?World-wide network of mirrorsFully indexed extensionsRESTful metadata distributionSearch and documentation site
    183. 183. Why PGXN?World-wide network of mirrorsFully indexed extensionsRESTful metadata distributionSearch and documentation siteComprehensive REST API
    184. 184. Why PGXN?World-wide network of mirrorsFully indexed extensionsRESTful metadata distributionSearch and documentation siteComprehensive REST APICommand-line client
    185. 185. pair
    186. 186. pair
    187. 187. PGXN Client%
    188. 188. PGXN Client%

sudo
easy_install
pgxnclientInstalling
pgxncli.py
script
to
/usr/local/binInstalling
pgxn
script
to
/usr/local/binProcessing
dependencies
for
pgxnclientFinished
processing
dependencies
for
pgxnclient%
    189. 189. PGXN Client%

sudo
easy_install
pgxnclientInstalling
pgxncli.py
script
to
/usr/local/binInstalling
pgxn
script
to
/usr/local/binProcessing
dependencies
for
pgxnclientFinished
processing
dependencies
for
pgxnclient

pgxn
install
pair%INFO:
best
version:
pair
0.1.3INFO:
saving
/tmp/pair‐0.1.3.zipINFO:
unpacking:
/tmp/pair‐0.1.3.zipINFO:
building
extensionINFO:
installing
extension%
    190. 190. PGXN Client%

sudo
easy_install
pgxnclientInstalling
pgxncli.py
script
to
/usr/local/binInstalling
pgxn
script
to
/usr/local/binProcessing
dependencies
for
pgxnclientFinished
processing
dependencies
for
pgxnclient

pgxn
install
pair%INFO:
best
version:
pair
0.1.3INFO:
saving
/tmp/pair‐0.1.3.zipINFO:
unpacking:
/tmp/pair‐0.1.3.zipINFO:
building
extensionINFO:
installing
extension%
    191. 191. PGXN Client%

sudo
easy_install
pgxnclientInstalling
pgxncli.py
script
to
/usr/local/binInstalling
pgxn
script
to
/usr/local/binProcessing
dependencies
for
pgxnclientFinished
processing
dependencies
for
pgxnclient

pgxn
install
pair%INFO:
best
version:
pair
0.1.3INFO:
saving
/tmp/pair‐0.1.3.zipINFO:
unpacking:
/tmp/pair‐0.1.3.zipINFO:
building
extensionINFO:
installing
extension
%
pgxn
load
pair
‐d
try

INFO:
best
version:
pair
0.1.3CREATE
TYPECREATE
FUNCTIONCREATE
OPERATOR%
    192. 192. Thank youDaniele Varrazzo
    193. 193. Coming in 9.1
    194. 194. Coming in 9.19.1 adding extension support
    195. 195. Coming in 9.19.1 adding extension supportCREATE
EXTENSION
pair
WITH
SCHEMA
utils;
    196. 196. Coming in 9.19.1 adding extension supportCREATE
EXTENSION
pair
WITH
SCHEMA
utils;Encapsulates dump/restore
    197. 197. Coming in 9.19.1 adding extension supportCREATE
EXTENSION
pair
WITH
SCHEMA
utils;Encapsulates dump/restoreWhatever is defined in the SQL script
    198. 198. Coming in 9.19.1 adding extension supportCREATE
EXTENSION
pair
WITH
SCHEMA
utils;Encapsulates dump/restoreWhatever is defined in the SQL scriptSupporting it is easy
    199. 199. 9.1 Extension Packaging
    200. 200. 9.1 Extension Packaging Control file
    201. 201. 9.1 Extension Packaging Control file Migration from unpackaged
    202. 202. 9.1 Extension Packaging Control file Migration from unpackaged pair--unpackaged--0.1.0.sql
    203. 203. 9.1 Extension Packaging Control file Migration from unpackaged pair--unpackaged--0.1.0.sql Properly-named SQL script
    204. 204. 9.1 Extension Packaging Control file Migration from unpackaged pair--unpackaged--0.1.0.sql Properly-named SQL script pair--0.1.0.sql
    205. 205. Create the control filepair.control
    206. 206. Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir/pairrelocatable
=
truesuperuser
=
false pair.control
    207. 207. Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir/pairrelocatable
=
truesuperuser
=
false pair.control
    208. 208. Create the control file Optional#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir/pairrelocatable
=
truesuperuser
=
false pair.control
    209. 209. Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir/pairrelocatable
=
truesuperuser
=
false pair.control
    210. 210. Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir/pairrelocatable
=
truesuperuser
=
false pair.control
    211. 211. Create the control file#
pair
extension For Ccomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0 codemodule_pathname
=
$libdir/pairrelocatable
=
truesuperuser
=
false pair.control
    212. 212. Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir/pairrelocatable
=
truesuperuser
=
false pair.control
    213. 213. Create the control file#
pair
extension Can movecomment
=
A
key/value
pair
data
type schemasdefault_version
=
0.1.0module_pathname
=
$libdir/pairrelocatable
=
truesuperuser
=
false pair.control
    214. 214. Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir/pairrelocatable
=
truesuperuser
=
false pair.control
    215. 215. Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir/pairrelocatable
=
true Not requiredsuperuser
=
false to install pair.control
    216. 216. Create the control file#
pair
extensioncomment
=
A
key/value
pair
data
typedefault_version
=
0.1.0module_pathname
=
$libdir/pairrelocatable
=
truesuperuser
=
false pair.control pair.control
    217. 217. Migration from Unpackagedsql/pair‐‐unpac…
    218. 218. Migration from UnpackagedALTER
EXTENSION
pair
ADD
TYPE
pair;ALTER
EXTENSION
pair
ADD
FUNCTION
pair(anyelement,
text);ALTER
EXTENSION
pair
ADD
FUNCTION
pair(text,
anyelement);ALTER
EXTENSION
pair
ADD
FUNCTION
pair(anyelement,
anyelement);ALTER
EXTENSION
pair
ADD
FUNCTION
pair(text,
text);ALTER
EXTENSION
pair
ADD
OPERATOR
~>(text,
anyelement);ALTER
EXTENSION
pair
ADD
OPERATOR
~>(anyelement,
text);ALTER
EXTENSION
pair
ADD
OPERATOR
~>(anyelement,
anyelement);ALTER
EXTENSION
pair
ADD
OPERATOR
~>(text,
text); sql/pair‐‐unpac…
    219. 219. Migration from UnpackagedALTER
EXTENSION
pair
ADD
TYPE
pair;ALTER
EXTENSION
pair
ADD
FUNCTION
pair(anyelement,
text);ALTER
EXTENSION
pair
ADD
FUNCTION
pair(text,
anyelement);ALTER
EXTENSION
pair
ADD
FUNCTION
pair(anyelement,
anyelement);ALTER
EXTENSION
pair
ADD
FUNCTION
pair(text,
text);ALTER
EXTENSION
pair
ADD
OPERATOR
~>(text,
anyelement);ALTER
EXTENSION
pair
ADD
OPERATOR
~>(anyelement,
text);ALTER
EXTENSION
pair
ADD
OPERATOR
~>(anyelement,
anyelement);ALTER
EXTENSION
pair
ADD
OPERATOR
~>(text,
text); sql/pair--unpackaged--0.1.0.sql sql/pair‐‐unpac…
    220. 220. Update the MakefileMakefile
    221. 221. Update the MakefileEXTENSION



=
pairEXTVERSION


=
$(shell
grep
default_version


$(EXTENSION).control
|

sed
‐e


"s/default_version[
]*=[
]*([^]*)/1/")DATA








=
$(filter‐out
$(wildcard
sql/*‐‐*.sql),$(wildcard
sql/*.sql))DOCS








=
$(wildcard
doc/*.*)TESTS







=
$(wildcard
test/sql/*.sql)REGRESS





=
$(patsubst
test/sql/%.sql,%,$(TESTS))REGRESS_OPTS
=
‐‐inputdir=testMODULES





=
$(patsubst
%.c,%,$(wildcard
src/*.c))PG_CONFIG



=
pg_configPG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes) Makefile
    222. 222. Update the MakefileEXTENSION



=
pairEXTVERSION


=
$(shell
grep
default_version


$(EXTENSION).control
|

sed
‐e


"s/default_version[
]*=[
]*([^]*)/1/")DATA








=
$(filter‐out
$(wildcard
sql/*‐‐*.sql),$(wildcard
sql/*.sql))DOCS








=
$(wildcard
doc/*.*)TESTS







=
$(wildcard
test/sql/*.sql)REGRESS





=
$(patsubst
test/sql/%.sql,%,$(TESTS))REGRESS_OPTS
=
‐‐inputdir=testMODULES





=
$(patsubst
%.c,%,$(wildcard
src/*.c))PG_CONFIG



=
pg_configPG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes) Makefile
    223. 223. Update the MakefileEXTENSION



=
pairEXTVERSION


=
$(shell
grep
default_version


$(EXTENSION).control
|

sed
‐e


"s/default_version[
]*=[
]*([^]*)/1/")DATA








=
$(filter‐out
$(wildcard
sql/*‐‐*.sql),$(wildcard
sql/*.sql))DOCS








=
$(wildcard
doc/*.*)TESTS







=
$(wildcard
test/sql/*.sql)REGRESS





=
$(patsubst
test/sql/%.sql,%,$(TESTS))REGRESS_OPTS
=
‐‐inputdir=testMODULES





=
$(patsubst
%.c,%,$(wildcard
src/*.c))PG_CONFIG



=
pg_configPG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes) Makefile
    224. 224. Update the Makefile Extract fromEXTENSION



=
pairEXTVERSION


=
$(shell
grep
default_version
 control file

$(EXTENSION).control
|

sed
‐e


"s/default_version[
]*=[
]*([^]*)/1/")DATA








=
$(filter‐out
$(wildcard
sql/*‐‐*.sql),$(wildcard
sql/*.sql))DOCS








=
$(wildcard
doc/*.*)TESTS







=
$(wildcard
test/sql/*.sql)REGRESS





=
$(patsubst
test/sql/%.sql,%,$(TESTS))REGRESS_OPTS
=
‐‐inputdir=testMODULES





=
$(patsubst
%.c,%,$(wildcard
src/*.c))PG_CONFIG



=
pg_configPG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes) Makefile
    225. 225. Update the MakefileEXTENSION



=
pairEXTVERSION


=
$(shell
grep
default_version


$(EXTENSION).control
|

sed
‐e


"s/default_version[
]*=[
]*([^]*)/1/")DATA








=
$(filter‐out
$(wildcard
sql/*‐‐*.sql),$(wildcard
sql/*.sql))DOCS








=
$(wildcard
doc/*.*)TESTS







=
$(wildcard
test/sql/*.sql)REGRESS





=
$(patsubst
test/sql/%.sql,%,$(TESTS))REGRESS_OPTS
=
‐‐inputdir=testMODULES





=
$(patsubst
%.c,%,$(wildcard
src/*.c))PG_CONFIG



=
pg_configPG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes) Makefile
    226. 226. Update the MakefileEXTENSION



=
pairEXTVERSION


=
$(shell
grep
default_version


$(EXTENSION).control
|

sed
‐e


"s/default_version[
]*=[
]*([^]*)/1/")DATA








=
$(filter‐out
$(wildcard
sql/*‐‐*.sql),$(wildcard
sql/*.sql))DOCS








=
$(wildcard
doc/*.*)TESTS







=
$(wildcard
test/sql/*.sql)REGRESS





=
$(patsubst
test/sql/%.sql,%,$(TESTS))REGRESS_OPTS
=
‐‐inputdir=testMODULES





=
$(patsubst
%.c,%,$(wildcard
src/*.c))PG_CONFIG



=
pg_configPG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes) Makefile
    227. 227. Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes) Makefile
    228. 228. Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlsql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql
 cp
$<
$@DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlEXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlendifPGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)include
$(PGXS) Makefile
    229. 229. Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlsql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql
 cp
$<
$@DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlEXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlendifPGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)include
$(PGXS) Makefile
    230. 230. Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlsql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql
 cp
$<
$@DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlEXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlendifPGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)include
$(PGXS) Makefile
    231. 231. Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlsql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql
 cp
$<
$@DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlEXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlendifPGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)include
$(PGXS) Makefile
    232. 232. Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlsql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql
 cp
$<
$@DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlEXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlendifPGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)include
$(PGXS) Makefile
    233. 233. Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlsql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql
 cp
$<
$@DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlEXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlendifPGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)include
$(PGXS) Makefile
    234. 234. Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlsql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql
 cp
$<
$@DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlEXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlendifPGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)include
$(PGXS) Makefile
    235. 235. Update the MakefilePG91

=
$(shell
$(PG_CONFIG)
‐‐version


|
grep
‐qE
"
8.|
9.0"
&&
echo
no
||
echo
yes)ifeq
($(PG91),yes)all:
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlsql/$(EXTENSION)‐‐$(EXTVERSION).sql:
sql/$(EXTENSION).sql
 cp
$<
$@DATA
=
$(wildcard
sql/*‐‐*.sql)
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlEXTRA_CLEAN
=
sql/$(EXTENSION)‐‐$(EXTVERSION).sqlendifPGXS
:=
$(shell
$(PG_CONFIG)
‐‐pgxs)include
$(PGXS) Makefile
    236. 236. Or Forget It
    237. 237. Or Forget ItCopy Makefile
    238. 238. Or Forget ItCopy MakefileEdit first line
    239. 239. Or Forget ItCopy MakefileEdit first line EXTENSION=pair
    240. 240. Or Forget ItCopy MakefileEdit first line EXTENSION=pairIgnore the rest
    241. 241. Or Forget ItCopy MakefileEdit first line EXTENSION=pairIgnore the rest Better still…
    242. 242. Skeleton in the Closet%
    243. 243. Skeleton in the Closet%

sudo
gem
install
pgxn_utilsInstalling
ri
documentation
for
pgxn_utils‐0.1.2...Installing
RDoc
documentation
for
pgxn_utils‐0.1.2...%
    244. 244. Skeleton in the Closet%

sudo
gem
install
pgxn_utilsInstalling
ri
documentation
for
pgxn_utils‐0.1.2...Installing
RDoc
documentation
for
pgxn_utils‐0.1.2...

pgxn_utils
skeleton
semver%





create

semver





create

semver/semver.control





create

semver/META.json





create

semver/Makefile





create

semver/README.md





create

semver/doc/semver.md





create

semver/sql/semver.sql





create

semver/sql/uninstall_semver.sql





create

semver/test/expected/base.out





create

semver/test/sql/base.sql%
    245. 245. Thank youDickson S. Guedes
    246. 246. Install Extension%
    247. 247. Install Extension

pgxn
load
pair
‐d
try%INFO:
best
version:
pair
0.1.2CREATE
EXTENSION% Nice.
    248. 248. Resources
    249. 249. Resourceshttp:/ /pgxn.org/ — Browse
    250. 250. Resourceshttp:/ /pgxn.org/ — Browsehttp:/ /manager.pgxn.org/ — Release
    251. 251. Resourceshttp:/ /pgxn.org/ — Browsehttp:/ /manager.pgxn.org/ — Releasehttp:/ /s.coop/pgextdocs — Learn
    252. 252. Resourceshttp:/ /pgxn.org/ — Browsehttp:/ /manager.pgxn.org/ — Releasehttp:/ /s.coop/pgextdocs — Learnhttp:/ /s.coop/4y4 — Slides
    253. 253. Resourceshttp:/ /pgxn.org/ — Browsehttp:/ /manager.pgxn.org/ — Releasehttp:/ /s.coop/pgextdocs — Learnhttp:/ /s.coop/4y4 — Slideshttp:/ /s.coop/pgxnclient — Client
    254. 254. Resourceshttp:/ /pgxn.org/ — Browsehttp:/ /manager.pgxn.org/ — Releasehttp:/ /s.coop/pgextdocs — Learnhttp:/ /s.coop/4y4 — Slideshttp:/ /s.coop/pgxnclient — Clienthttp:/ /s.coop/pgxnutils — Develop
    255. 255. Building and Distributing PostgreSQL Extensions Without Learning C David E. Wheeler PostgreSQL Experts, Inc. PGXPUG PgDay 2011Text: Attribution-Noncommercial-Share Alike 3.0 United States:http://creativecommons.org/licenses/by-nc-sa/3.0/us/Images licensed independently and © Their respective owners.

    ×