Skip to content

Commit

Permalink
Bringing in the DBA course (#41)
Browse files Browse the repository at this point in the history
* up to date (#1)

* Cleanup

* Use latest version of docker

* Remove Docker first

* Remove more of Docker

* Start Docker after installing

* fixed misspecification

* breaking apart the workshop

* beginning

* finished first page and added to the dir

* more scaffolding

* more progress

* mid progress in page 3

* a little more

* done for now

* fixing commands

* normal typo and bug finding

* typo fixes from @davecramer

* more general cleanup

* trying to see pgadmin4

* missing hyphens matter

* trying later version of pgadmin

* roll that back

* think I got it

* think I got it

* Modified property to show correctly the tabs (#3)

Signed-off-by: Gabriela S. Soria <[email protected]>

* some bug fixes and new module

* fixing image

* fixing image

* Fixing functions

* Beginnings of pgadmin class

* getting the DBA stuff in
  • Loading branch information
thesteve0 authored Jul 26, 2019
1 parent aa3d714 commit f93e3f6
Show file tree
Hide file tree
Showing 19 changed files with 688 additions and 15 deletions.
6 changes: 2 additions & 4 deletions basic-postgresql-devel/basicfunctions/index.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@
}
},
"environment": {
"showdashboard": true,
"uilayout": "terminal",
"uimessage1": "\u001b[32mYour Interactive Bash Terminal.\u001b[m\r\n",
"terminals": [
{"name": "Terminal 2", "target": "host01"}
]
"dashboards": [{"name": "PgAdmin4", "port": "5050"}]
},
"backend": {
"imageid": "crunchydata-single1"
Expand Down
7 changes: 5 additions & 2 deletions basic-postgresql-devel/basicfunctions/set-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@

echo 'please wait while we prep the environment (should take about 10 seconds)'
echo 'starting the database'
docker run -d -p 5432:5432 -e PG_USER=groot -e PG_PASSWORD=password -e PG_DATABASE=workshop --name=pgsql thesteve0/postgres-appdev
docker network create mybridge

docker run -d --network mybridge -p 5050:5050 -e PGADMIN_SETUP_EMAIL=admin -e PGADMIN_SETUP_PASSWORD=password --name=pgadmin crunchydata/crunchy-pgadmin4:centos7-11.2-2.3.1

docker run -d --network mybridge -p 5432:5432 -e PG_USER=groot -e PG_PASSWORD=password -e PG_DATABASE=workshop --name=pgsql thesteve0/postgres-appdev

until PGPASSWORD="password" psql -h localhost -U groot postgres -c '\l' &> /dev/null; do
echo >&2 "$(date +%Y%m%dt%H%M%S) Waiting for Postgres to start"
sleep 1
done


PGPASSWORD="password" psql -h localhost -U groot workshop
54 changes: 54 additions & 0 deletions basic-postgresql-devel/basicpgadmin/01-connection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Connecting to our Database

If you look at the second tab in our learning interface it says PgAdmin4. Go ahead and click that tab which should spawn a new browser window or tab. You may have to wait a few seconds while the PGAdmin4 interface gets rendered. You should now be able
to login using username _admin_ and password _password_.

## First screen

You should be seeing a screen that looks like this:

![Home Screen](basicpgadmin/assets/01-home-screen.png)

This is the home page for PgAdmin4. Before we can do anything meaningful we need to connect to a PostgreSQL server. There are two ways to do this:

1. Click on the "Add New Server" butting in the middle of the screen.
1. Right click on the Servers icon in the top left of the left navigation panel.

Let's start by doing the right click option because it will give us more flexibility.

### Right click options

![Create Server](basicpgadmin/assets/01-server-selection.png)

When you right click on the icon you should see a list of options. Go ahead and mouse over _create_. This will then show _server group_ and _server_. If you are going to manage and interact with a lot of PostgreSQL servers then you might want to create server groups such as testing vs production. Server groups is just a logical organization in the PgAdmin interface.



In our case since we are only managing one server, go ahead and click server to bring up the new create server dialog.

## Create Server Dialog

While there are quite a few boxes and tabs in this dialog, only a few of them are actually required to create a new server in PgAdmin.

### First Tab

![First Tab](basicpgadmin/assets/01-create-server-tab1.png)

On the general tab, the only field required is _Name_. This name represents the name YOU want to use to identify this PostgreSQL server in the PgAdmin interface. You could call it monkey if you want - it doesn't **require** any relation to a name in the real world.

For today's exercise let's name it "Workshop"

In the comment field you can put in some information about this server connection. The information can be brought up in the interface later and can help you remember facts about this server.

Once you are done filling in those fields go ahead and click on the second tab titled _Connection_.

### Second Tab

This _Connection_ tab contains most of the important information about your server.

![Second Tab](basicpgadmin/assets/01-create-server-tab2.png)


## Wrap Up
We just finished the basic skeleton of a function: declaration, function name, parameters, return type, code block, and
language used. In our next exercise we will explore doing more with the function declaration and parameters.
135 changes: 135 additions & 0 deletions basic-postgresql-devel/basicpgadmin/02-navigation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Function Declarations and Parameters

While our function is amazing, perhaps we do want to do a bit of editing to it. Maybe accept some parameters or change
the code.

## Create versus Replace

Functions are immutable, there is no way to edit them in place. So while we used CREATE in our first example, the
recommended pattern is to instead say "CREATE or REPLACE FUNCTION"

This tells PostgreSQL if this function doesn't exist then create it otherwise, replace the one that is there. From now on
we are going to use this so we can keep iterating on our function.

## Parameterising Our Function

You know the next step in our function right? Of course we need to get it so say "Hello [your name]". Let's start with the
simplest way possible

```
CREATE OR REPLACE FUNCTION brilliance(varchar)
RETURNS VARCHAR AS
```{{execute}}
We are saying that this function requires the caller to pass in a string along with the function call. From the PostgreSQL
documentation:
The argument types can be base, composite, or domain types, or can reference the type of a table column.
Just use the type as if you were defining a column in a table.
### Referencing the Parameter in the Code
Now we can use this parameter in the code.
```
$$
SELECT 'hello ' || $1;
$$
LANGUAGE sql;
```{{execute}}
If we had used more parameters we would keep incrementing the $number for each new parameter. The '||' is the concatenation
operator per the SQL standard.
Let's go ahead and use our cool new function!
```
select brilliance('student');
```{{execute}}
Go ahead and change the name to anything else you want to try.
## Better Parameter Names
While it was easy to just put in 'varchar' for the parameter, that is not as easy to use and read in the body of our code.
Let's clean this up and make more literate code. You can give a name to the parameter and it appears right before the type
declaration. We will also add another parameter so you can see how to handle passing in multiple named variables.
```
CREATE OR REPLACE FUNCTION brilliance(name varchar, rank int)
RETURNS VARCHAR AS
$$
SELECT 'hello ' || name || '! You are number ' || rank;
$$
LANGUAGE sql;
```{{execute}}
This code is much more readable and maintainable. We also got to see that the '||' operator will work with non-string types
as long as there is one string type in the concatenation.
Time to exercise our function again:
```
select brilliance('student', 1);
```{{execute}}
## Default Values for Parameters
It is also possible to specify default values for parameters for when the function is called without a value for a parameter.
**Note**, if you have a list of parameters, once you specify a default value for a parameter ALL following parameters must
have default values as well.
Let's go ahead and specify default values for both parameters in our great new function.
```
CREATE OR REPLACE FUNCTION brilliance(name varchar = 'Jorge', rank int = 7)
RETURNS VARCHAR AS
$$
SELECT 'hello ' || name || '! You are number ' || rank;
$$
LANGUAGE sql;
```{{execute}}
And now if we call our function we will get those values if we don't specify a value. Before we do this we need to drop our
original function - think about why?....
```
DROP FUNCTION brilliance();
```
Since we already wrote a function named brilliance in step 1 that accepted no parameters, it is going to try and use that
rather than what we want to happen here.
```
select brilliance();
```{{execute}}
But we can also specify only one parameter and use the parameter name. Read more in the [official docs](https://www.postgresql.org/docs/11/sql-syntax-calling-funcs.html) about how
to call functions:
```
select brilliance(rank => 1);
```{{execute}}
## Wrap Up
**NOTE** No two functions can have the same name UNLESS they have different parameter signatures.
For example, you can't have two functions named _myfunction_ unless one is myfunction()
and the other is myfunction(myparam varchar). You could actually have functions:
1. myfunction()
1. myfunction(varchar)
1. myfunction(int)
As long as the parameters are different (order does not matter) they can co-exist. What this also means is that you can
overload a function to do different behavior depending on the types passed in.As we saw above, having default values
along with over-ridden function names can sometimes cause issues for the users of the functions.
Keep this in mind if you run into an error or trying to determine how to architect your functions.
Though we covered the basics of adding parameters to your functions we will return to this as we move on to the next section.
In the next exercise we will cover different ways to return values and how to handle different return types.
128 changes: 128 additions & 0 deletions basic-postgresql-devel/basicpgadmin/03-sql.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Return Types

For this final section we are going to cover all the ways you can return data from your functions as well how to handle
different data return types. Let's move on from just returning a single value and perhaps throw in a little Pl/PGSQL the
procedural language included with PostgreSQL.

We will start by showing how the actual RETURN statement is not necessary.

## IN, OUT, and INOUT Paremeters

PostgreSQL allows you to specify the "direction" data flows into a function based on how you declare the parameter. By
default all parameters are specified as IN parameters, meaning data is only passed into the function with the parameter.

If you declare a parameter as OUT then data will be returned. You can have more than one OUT parameter in a function. Using a
RETURNS {type} AS statement when you have OUT parameters is optional. If you
use the RETURNS {type}AS statement with your function, it's type must match the type of our OUT parameters.

An INOUT parameter means it will be used to pass data in and then return data through this parameter name as well

For our function let's get rid of the RETURNS {type} AS statement and add another OUT parameter. We have to drop our function first
because Postgresql does NOT consider OUT parameters as a change in signature but by adding the OUTs we are changing the
return type.

```
DROP FUNCTION brilliance(varchar, int);
CREATE OR REPLACE FUNCTION
brilliance(name varchar = 'Jorge', rank int = 7, out greetings varchar, out word_count int)
AS
```{{execute}}
We left the original signature in place but now we added two OUT parameters: 1) greetings which will hold the full statement
and 2) word_count which will hold the count of characters in the greetings parameter.
Now let's alter the body of the text.
```
$$
BEGIN
greetings := (SELECT 'hello ' || name || '! You are number ' || rank);
word_count := length(greetings);
END;
$$
LANGUAGE plpgsql;

```{{execute}}
Remember that if we left the language as SQL it would only return the final value and we couldn't really do the assignment of
values to parameters. So we change the language to Pl/PGSQL. Now we get access to the **:=** [assignment operator}(https://www.postgresql.org/docs/11/plpgsql-statements.html).
Because we are using PL/PGSQL we also need to wrap out code in BEGIN and END; statements. Finally, the := operator can only be used
for assignment of a single value or a single row we need to wrap our select statement in ( ) to coerce to a single value.
### Using this new and improved function:
When we use this function:
```
select brilliance();
```{{execute}}
Notice we get a different type of result:
```
workshop=> select brilliance();
brilliance
--------------------------------------
("hello Jorge! You are number 7",29)
(1 row)

```
We get a single column result with a name matching the function name. For the value we get an array containing our two values.
You can think of this as PostgreSQL coercing all our OUT variables into an array. It would continue appending to
the array for every OUT or INOUT variable we declared. Technically what happened is the our result is actually created an
anonymous record type to hold the output.
Let's make it a bit nicer to read:
```
select * from brilliance();
```{{execute}}
Which should give you a result like this:
```
workshop=> select * from brilliance();
greetings | word_count
-------------------------------+------------
hello Jorge! You are number 7 | 29
(1 row)

```
Now we get a row result but the column names match the OUT variable names. Again if we add more OUT variables the result
would just have more columns.
## Different Data Types to Return
So far all we have returning simple values that match base types in SQL. As mentioned earlier you can
return anything you can use to define a column in a table, even your custom defined type.
Quite often you are going to want to return a row in a table or perhaps a whole table (or use them as OUT parameters):
1. RETURNS RECORD - A record can be thought of as a single row in an arbitrary table.
1. RETURNS {tablename} - If you want a row to obey the schema of a table you can just pass in the table name.
1. RETURNS SETOF RECORD - By adding the SETOF to the statement you can now return multiple records (rows)
1. RETURNS SETOF {tablename} - And by extension, this will return multiple rows with a schema that obeys the table schema
Specific to the RETURNS X AS, you can actually define a table in the place of X. For example:
```
CREATE FUNCTION my-little-table()
RETURNS TABLE (id int, name text, quarter tsrange)
As $$
```
## Wrap Up
With that we have concluded our basic introduction to PostgreSQL functions. We did not actually go into specifics of PL/PGSQL
or PL/Python nor did we cover any of the more advanced ways of working with function results like Lateral Joins and such.
Those will be topics for later classes.
The main goal was really to get you to understand the basic structure of functions, how to pass data in and out, and get
your hands dirty. Hopefully you now have a good foundation for diving into [more of](https://www.postgresql.org/docs/current/plpgsql.html)
the [core documentation](https://www.postgresql.org/docs/current/extend.html).
Loading

0 comments on commit f93e3f6

Please sign in to comment.