Postgres literal escape sequences

2019-10-09

Postgres automatically escapes all occurrences of escape sequences if strings aren't prefixed with the E escape constant. This can lead to unexpected results:

select array_to_string(array['first line', 'second line', 'third line'], '\n');
           array_to_string
-------------------------------------
 first line\nsecond line\nthird line

Prefixing the separator string with the escape constant tells Postgres to interpret the sequence literally.

select array_to_string(array['first line', 'second line', 'third line'], E'\n');
 array_to_string
-----------------
 first line     +
 second line    +
 third reich

There's no need to prefix the template string when using format, as E-strings are substituted in literally.

select format('first line%ssecond line', E'\n');
   format
-------------
 first line +
 second line

CSS class grouping

2019-10-07

In HTML, any symbol or undefined class name is ignored. Thus, you can use custom separators to group your classes.

<div class="sm-8 lg-3 bg-black fg-green custom-class1" />
<div class="sm-8 lg-3 / bg-black fg-green / custom-class1" />
<div class="[ sm-8 lg-3 ] [ bg-black fg-green ] [ custom-class1 ]" />

How to chain multiple functions in Javascript properly with async/await

2019-10-04

The code below chains multiple functions waits for everything to resolve, and then sends the result:

// chain any number of async functions
const asyncChain = (...fns) => x => fns.reduce(async (res, fn) => fn(await res), x);

// async functions
const add = async x => x + 1;
const multiply = async x => x * 2;
const square = async x => x * x;

const getResult = asyncChain(add, multiply, square);

(async () => console.log(await getResult(4)))(); // 100

Typescript function parameter type

2019-10-02

A previous post mentioned the Typescript ReturnType utility type. It is really handy to retrieve the return type of a function.

But there is also a type to retrieve the parameter list of a function. Meet Parameters<T>.

const add = (first: number, second: number) => first + second;

const cache = <T extends (...args: any) => any>(func: T) => {
  const cacheObject = {};
  return (...args: Parameters<T>) => {
    const hashedArgs = JSON.stringify(args);
    if (cacheObject.hasOwnProperty(hashedArgs)) {
      return cacheObject[hashedArgs];
    } else {
      return (cacheObject[hashedArgs] = func(...args));
    }
  };
};

See the full example here.

The code can be found at lib/lib.es5.d.ts:

/**
 * Obtain the parameters of a function type in a tuple
 */
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

Quickly test whether ports can be reached on your linux machine

2019-10-01

If you've ever tried to launch anything that opens a port on your linux machine and you'd like to know whether you've misconfigured something (e.g. the listen address), or whether you're running into firewall issues, you'll be grateful to find out that there's a simple way to test for firewall issues.

There's a tool called netcat; It's part of almost any linux server setup I've worked on and it can be launched by typing nc on your command line. netcat has two modes:

  • a calling mode
  • a listening mode

We can use both of these, on two sides of our server to check if the firewall will let us pass or not.

Introduction to netcat

On the machine where you'd like to run the server, which will be listen on a given port, say 54321, run this to open a listening netcat:

nc -vlk 54321

I'll break those arguments down for you:

  • v is for verbose: print more details about connections
  • l is for listening mode
  • k is for keep-open; This means that netcat will accept connections over and over again.
  • 54321 is for the listening port. By default, netcat listens on all IP-addresses it can find, but you can also limit it to a given IP-address, say your primary IP-address, so your service can be reached from your network, but not via localhost, for example.

You can stop netcat by sending an interrupt signal (Ctrl + c).

Now, on the second machine, we'll launch another netcat instance, which is set to connect to the first machine, by IP-address (or even hostname) and by port:

nc -vz 192.168.178.24 54321

Here, the arguments have the following meaning:

  • v is still for verbose
  • z is for Zero-I/O mode, meaning that netcat will initiate a connection and report success or failure, but won't transmit any data
  • 192.168.178.24 is my first machine's local IP-address
  • 54321 is the port that we'd like to connect to.

Please be aware that it's impossible to bind two applications to the same port. So as long as you have a netcat running that listens on port 54321, trying to launch another application that should listen on 54321 will fail. If in doubt, run sudo killall nc to kill all netcats that may still be running in the background (If you launched the netcats detached, by adding a postfix & to your nc call).

Do some testing

If you now run these two commands in different terminals, it might look like this:

  1. Open the port
[[email protected] ~]$ nc -vlk 54321
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::54321
Ncat: Listening on 0.0.0.0:54321

Note, that netcat now is listening on all IPv6 addresses (:: is an IPv6 address wildcard) and all IPv4 addresses (0.0.0.0 is an IPv4 wildcard) registered on your system.

  1. Try to reach it
[[email protected] ~]$ nc -vz 192.168.178.24 54321
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Connected to 192.168.178.24:54321.
Ncat: 0 bytes sent, 0 bytes received in 0.01 seconds.
  1. Now, your first terminal will show this:
[[email protected] ~]$ nc -vlk 54321
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::54321
Ncat: Listening on 0.0.0.0:54321
Ncat: Connection from 192.168.178.25.
Ncat: Connection from 192.168.178.25:60842.

In this scenario, we succeeded with our testing. The port is open and if I now try to make my application available from my own network, I'll only have to troubleshoot the application itself and the configured listening address.

If a firewall was running that prevents my system from accepting connections through the specific port, I'd get something like this, and no new lines from the command on my first system:

[[email protected] ~]$ nc -vz 192.168.178.24 54321
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Connection refused.

You can also go ahead and experiment with netcat and learn something about how listening addresses work: run nc -vlk 127.0.0.1 54321 and nc -vz 192.168.178.24 54321 or nc -vlk 192.168.178.24 54321 and nc -vz 127.0.0.1 54321. Try what happens if you swap the IP-addresses around (Of course, you need to replace my IP-address with your own.).

Also, try what happens if you use the wrong wildcard: nc -vlk :: 54321 or nc -vlk 0.0.0.0 54321. In the former case, you'll only be able to connect to your machine via IPv6. In the latter case, you can only use IPv4 connections. You could then reach it via a hostname and tell the connecting netcat to only use IPv4 or IPv6 connections: nc -vz6 hostname 54321 and nc -vz4 hostname 54321

[[email protected] ~]$ nc -vlk :: 54321
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Listening on :::54321
Ncat: Connection from 2003:dd:ff2f:d800:f4aa:8a99:f084:ee31.
Ncat: Connection from 2003:dd:ff2f:d800:f4aa:8a99:f084:ee31:53090.
[[email protected] ~]$ nc -vz6 linux1 54321
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Connected to 2003:dd:ff2f:d800:f4aa:8a99:f084:dd41:54321.
Ncat: 0 bytes sent, 0 bytes received in 0.02 seconds.
[[email protected] ~]$ nc -vz4 linux1 54321
Ncat: Version 7.70 ( https://nmap.org/ncat )
Ncat: Connection refused.

Enhance Redux props with ReturnType

2019-09-30

The following example is built in the offical Redux documentation:

import { AppState } from './store'

import { SystemState } from './store/system/types'

import { ChatState } from './store/chat/types'

interface AppProps {
  chat: ChatState
  system: SystemState
}

const mapStateToProps = (state: AppState) => ({
  system: state.system,
  chat: state.chat
})

Here, SystemState and ChatState are imported and used manually.

But as AppState is correctly typed, we can use our beloved ReturnType instead:

import { AppState } from './store'

type AppProps = ReturnType<typeof mapStateToProps>

const mapStateToProps = (state: AppState) => ({
  system: state.system,
  chat: state.chat
})

Check out this CodeSandbox for a runnning example.


Composite types and NULL in PostgreSQL

2019-09-29

Basics

-- a row including only nulls is null
$ SELECT ROW(NULL, NULL) IS NULL;
-> true

-- a row including no nulls is not null
$ SELECT ROW(10, 10) IS NOT NULL;
-> true

Unusual

-- this row is not NULL
$ SELECT ROW(10, NULL) IS NULL;
-> false

-- but it's also not NOT NULL
$ SELECT ROW(10, NULL) IS NOT NULL;
-> false

-- there is a difference between `NOT ROW(...) IS NULL` and `ROW(...) IS NOT NULL`
$ SELECT NOT ROW(10, NULL) IS NULL;
-> true

Wait, what?

-- as mentioned above, a row including only nulls is null
$ SELECT ROW(NULL) IS NULL;
-> true

-- this does not apply recursively, though
$ SELECT ROW(ROW(NULL)) IS NULL;
-> false
What's going on there?

Values inside of a composite type are checked for NULL value equality, which is not the same as recursively checking with IS NULL!

This behavior is explained in this twitter post.

We can (ab)use this to check if a value is literally NULL or just a value that IS NULL:

SELECT  value              AS value,
        value      IS NULL AS is_null,
        ROW(value) IS NULL AS is_null_value
FROM    (VALUES (NULL), (ROW(NULL))) AS x(value);
 value  | is_null | is_null_value
--------+---------+---------------
 <null> | t       | t
 ()     | t       | f

Check out this blog post for more information on NULL behavior.


Git add partial files

2019-09-28

I often write lots of code at once and forget to commit. Then I have to create one huge commit with all changes, because I thought I can't partially stage and commit a file.

Turns out, you can do exactly that with git add -p!

This command lets you interactively select which hunks (blocks of changes) you want to stage.