Ignored By Dinosaurs πŸ¦•

postgres

Hi there and welcome back to Platform from scratch. Today we're going to take a very simple Laravel application that will make use of Postgres on the backend as a database.

The complete example application can be found here – https://github.com/JGrubb/platformsh-laravel-example

The very first step of this will be to add in the appropriate .platform/services.yaml file. This file was left intentionally empty in the setup for this Laravel application, as we didn't have a need for a working database and were just getting our app set up and running. Now however, we're going to add a very simple configuration into services.yaml.

# This is the "name" of this service and can be any arbitrary string.
# You could name this "foo" and that's the name you'd use in your
# .platform.app.yaml file in the next step.
pgsql:
  # This is the actual service you'll be using.
  type: postgresql:9.3
  # How much space you want to give this in megabytes. 
  # 1 gig will get us going
  disk: 1024

Over in .platform.app.yaml we'll add in the relationships section to the file.

relationships:
  # This config takes the form of "relationship_name: service_name:driver"
  #
  # Instead of "database", you could also call this "bar" and that's how it'll
  # show up in the $PLATFORM_RELATIONSHIPS environment variable. I'll show
  # you how that manifests itself in just a moment.
  #
  # "pgsql" in this case is the name you gave it in services.yaml and
  # "postgresql" is the part that you can't just arbitrarily name. That
  # is the hint to our container build system that you need a PG database
  # service.
  database: "pgsql:postgresql"

Commit this and push it up to your platform remote. This will trigger a rebuild of your application, and when that's done let's SSH into your environment with the Platform CLI – $ platform ssh.

Once you're in, try this – echo $PLATFORM_RELATIONSHIPS. You'll get a big base64 encoded string as a result. This is because you can't set complex objects or even JSON as an environment variable, so let's decode that by piping it to base64 --decode – echo $PLATFORM_RELATIONSHIPS | base64 --decode

This should give you back something like

{"database": [{"username": "main", "password": "main", "ip": "250.0.96.171", "host": "database.internal", "query": {"is_master": true}, "path": "main", "scheme": "pgsql", "port": 5432}]}.

sidebar

Just for the fun of it, let's try this in the relationships section -

relationships:
  dark_chocolate: "pgsql:postgresql"

Sure enough β€”

web@4ikq2xigwlw5s-master--app:~$ echo $PLATFORM_RELATIONSHIPS | base64 --decode
{"dark_chocolate": [{"username": "main", "password": "main", "ip": "250.0.96.171", "host": "big_daddy.internal", "query": {"is_master": true}, "path": "main", "scheme": "pgsql", "port": 5432}]}

So the basic gist of how you establish a connection to any kind of service that you set up in services.yaml should now be clearer than it was before, and we'll now set about adding the code to our Laravel app that will make use of these environment variables.

// at the top of config/database.php. This will decode the base64
// encoded envvar and expand it into the variables that Laravel is
// expecting.
if ($relationships = getenv('PLATFORM_RELATIONSHIPS')){
  $pltrels = json_decode(base64_decode($relationships), TRUE);
  $database = $pltrels['database'][0];
  putenv("DB_CONNECTION={$database['scheme']}");
  putenv("DB_HOST={$database['host']}");
  putenv("DB_PORT={$database['port']}");
  putenv("DB_DATABASE={$database['path']}");
  putenv("DB_USERNAME={$database['username']}");
  putenv("DB_PASSWORD={$database['password']}");
}

This particular piece of code is not Postgres specific, and in fact will work just fine with MySQL as well. The beauties of abstraction...

The final step in this process is optional, but if you want to have artisan migrate the database on deploy rather than logging into the server and running it manually you'd add this to the bottom of your hooks.deploy in .platform.app.yaml β€”

hooks:
  deploy:
    # other commands
    php artisan migrate --force

The --force flag will allow migrate to run in a β€œproduction” environment.

There is one final step to be aware of, and that's that the pdo_pgsql extension is not enabled by default in the PHP containers. You'll need to add this somewhere in .platform.app.yaml -

runtime:
  extensions:
    - pdo_pgsql

If you were using MySQL, this step would not be needed as pdo_mysql is enabled by default. Indeed, if you're using Postgres, you can disable the MySQL extension if you wish -

runtime:
  extensions:
    - pdo_pgsql
  disabled_extensions:
    - pdo_mysql

For reference, here's the complete .platform.app.yaml β€”

name: app
type: php:5.6
runtime:
  extensions:
    - pdo_pgsql
build:
  flavor: composer
disk: 2048
web:
  locations:
    "/":
      root: "public"
      index:
        - index.php
      allow: true
      passthru: "/index.php"
mounts:
  "/storage": "shared:files/storage"
  "/bootstrap/cache": "shared:files/bootstrap_cache"
relationships:
  database: "pgsql:postgresql"
hooks:
  deploy: |
    mkdir -p storage/app/public
    mkdir -p storage/framework/views
    mkdir -p storage/framework/sessions
    mkdir -p storage/framework/cache
    mkdir -p storage/logs
    php artisan migrate --force

#platformsh #postgres #laravel

I recently rebuilt this site (again, I think this is v9 now) into Rails and Postgres. I was moving out of Drupal, so there was a significant downsizing of the database, and some pretty painless migration of the data. Once again, I used the Sequel gem to write the migration script to pull out the posts and related data that gets scattered hither and yon in Drupal's database. That was the easy part.

I did all of this over a period of about two weeks and it was joyous, painless, and fun. I used the Textacular gem to power that full text search up there (if you're on a #{$medium-and-up} device) and generally got to know a teeny little bit more about Postgres – the grown up's open source database. This whole thing is just a step on the path of the continous quest toward β€œthe idea”, and some of Postgres' features that let you denormalize some easy data will definitely come in handy when I understand the fundamentals of the engine a little better. I just met one of them now.

Postgres sequences

When you want an auto-incrementing id as a primary key on your database table in MySQL, it's very straightforward. It's essentially a property of the column itself, ie

+-------+---------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
+-------+---------+------+-----+---------+----------------+

In postgres, which is a much more robust database, you have an entire β€œsequence” object available in the database. You can program these sequences as you would an object, so if for some reason you wanted to skip even numbers, you can do that. The first use case for this that comes to mind is sharding your dataset when scaling out, but it'll likely be never when I find that out in practice.

Anyway, when I migrated the blog data into the system it brought the numeric primary ids with it, but just now when I went to author my first blog post on the new system it bombed out when I went to save.

ERROR -- : PG::UniqueViolation: ERROR: duplicate key value violates unique constraint "posts_pkey"
DETAIL: Key (id)=(1) already exists.

If I hadn't read about sequences already, this would've confused the shit out of me. So the fix is to holler at your sequence object to start incrementing where you need it to, rather than 1, which is where it's currently set.

ibd_db=# select id from posts order by id desc limit 5;
  id
  -----
  251
  250
  244
  243
  239
(5 rows)

ibd_db=# alter sequence posts_id_seq restart with 252;

// Save post, no problems

ibd_db=# select id from posts order by id desc limit 5;
  id
  -----
  252
  251
  250
  244
  243
(5 rows)

So ALTER SEQUENCE sequence_name RESTART WITH next_id_in_line;.

#databases #postgres


The program "postgres" was found by "/usr/local/Cellar/postgresql/9.2.4/bin/initdb"
but was not the same version as initdb.

I've been battling this for the last couple of hours, trying to figure out why I can't make Postgres run as easily on my desktop as I did on my laptop. Homebrew took care of it all, just leaving me with the agony of taking off the MySQL training wheels to figure out this new and scary Postgres admin syntax.

So I uninstalled the Homebrew version and went to the EnterpriseDB site and downloaded the official installer for Mac. This didn't yield any results either, and seemed to want you to use the GUI tools to administer it anyway. which psql kept giving me /usr/bin/psql, which should've been more of a clue, but I'm not that quick. psql --version kept giving me 9.0.2, which also should've been more of a clue, but I just figured I must've installed Postgres a long time ago and gave up and forgot about it.

Then I remembered. Mac OSX server comes with Postgres. That's why it's reporting /usr/bin for all it's paths instead of /usr/local/bin (the homebrew default), or /Library/Postgres, the official installer default.

There was also an unkillable set of _postgres processes in ps that I couldn't figure out how to kill. So, the flamethrower method is to delete everything in /usr/bin that relates to PG – psql, postgres_real, anything you can find. Don't forget /usr/bin/initdb because that's what was throwing the above error. Then you can get on with the homebrew installer.

#postgres #devops