From cf7cc066ed86f36f46b2726e97c6c745eed21a51 Mon Sep 17 00:00:00 2001
From: Zhao Junwang <zhjwpku@gmail.com>
Date: Thu, 1 Aug 2024 13:49:53 +0000
Subject: [PATCH v1] official devcontainer config

Signed-off-by: Zhao Junwang <zhjwpku@gmail.com>
---
 .devcontainer/.gdbinit             |  32 +++
 .devcontainer/.psqlrc              |   7 +
 .devcontainer/Dockerfile           |  95 +++++++
 .devcontainer/devcontainer.json    |  46 ++++
 .devcontainer/gdbpg.py             | 386 +++++++++++++++++++++++++++++
 .devcontainer/launch.json          |  20 ++
 .devcontainer/postCreateCommand.sh |  86 +++++++
 .devcontainer/tasks.json           | 219 ++++++++++++++++
 .gitignore                         |   3 +
 9 files changed, 894 insertions(+)
 create mode 100644 .devcontainer/.gdbinit
 create mode 100644 .devcontainer/.psqlrc
 create mode 100644 .devcontainer/Dockerfile
 create mode 100644 .devcontainer/devcontainer.json
 create mode 100644 .devcontainer/gdbpg.py
 create mode 100644 .devcontainer/launch.json
 create mode 100644 .devcontainer/postCreateCommand.sh
 create mode 100644 .devcontainer/tasks.json

diff --git a/.devcontainer/.gdbinit b/.devcontainer/.gdbinit
new file mode 100644
index 0000000000..00ac2119d9
--- /dev/null
+++ b/.devcontainer/.gdbinit
@@ -0,0 +1,32 @@
+# gdbpg.py contains scripts to nicely print the postgres datastructures
+# while in a gdb session. Since the vscode debugger is based on gdb this
+# actually also works when debugging with vscode.
+source /root/gdbpg.py
+
+# when debugging postgres it is convenient to _always_ have a breakpoint
+# trigger when an error is logged. Because .gdbinit is sourced before gdb
+# is fully attached and has the sources loaded. To make sure the breakpoint
+# is added when the library is loaded we temporary set the breakpoint pending
+# to on. After we have added out breakpoint we revert back to the default
+# configuration for breakpoint pending.
+# The breakpoint is hard to read, but at entry of the function we don't have
+# the level loaded in elevel. Instead we hardcode the location where the
+# level of the current error is stored. Also gdb doesn't understand the
+# ERROR symbol so we hardcode this to the value of ERROR. It is very unlikely
+# this value will ever change in postgres, but if it does we might need to
+# find a way to conditionally load the correct breakpoint.
+set breakpoint pending on
+break elog.c:errfinish if errordata[errordata_stack_depth].elevel == 21
+set breakpoint pending auto
+
+echo \n
+echo ----------------------------------------------------------------------------------\n
+echo when attaching to a postgres backend a breakpoint will be set on elog.c:errfinish \n
+echo it will only break on errors being raised in postgres \n
+echo \n
+echo to disable this breakpoint from vscode run `-exec disable 1` in the debug console \n
+echo this assumes it's the first breakpoint loaded as it is loaded from .gdbinit \n
+echo this can be verified with `-exec info break`, enabling can be done with \n
+echo `-exec enable 1` \n
+echo ----------------------------------------------------------------------------------\n
+echo \n
diff --git a/.devcontainer/.psqlrc b/.devcontainer/.psqlrc
new file mode 100644
index 0000000000..7642a97149
--- /dev/null
+++ b/.devcontainer/.psqlrc
@@ -0,0 +1,7 @@
+\timing on
+\pset linestyle unicode
+\pset border 2
+\setenv PAGER 'pspg --no-mouse -bX --no-commandbar --no-topbar'
+\set HISTSIZE 100000
+\set PROMPT1 '\n%[%033[1m%]%M %n@%/:%>-%p%R%[%033[0m%]%# '
+\set PROMPT2 '  '
diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile
new file mode 100644
index 0000000000..3a09855faf
--- /dev/null
+++ b/.devcontainer/Dockerfile
@@ -0,0 +1,95 @@
+FROM debian:bookworm AS base
+
+ENV TZ=UTC
+RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
+
+# install build tools(alphabetic order)
+RUN apt update && apt install -y \
+    bash-completion \
+    bison \
+    bzip2 \
+    ccache \
+    cpanminus \
+    cron \
+    curl \
+    flex \
+    fswatch \
+    gcc g++ \
+    gdb \
+    git \
+    htop \
+    iproute2 \
+    libcurl4-gnutls-dev \
+    libgdal-dev \
+    libgeos-dev \
+    libicu-dev \
+    libkrb5-dev \
+    liblz4-dev \
+    libpam0g-dev \
+    libperl-dev \
+    libproj-dev \
+    libprotobuf-c-dev \
+    libreadline-dev \
+    libselinux1-dev \
+    libssl-dev \
+    libxml2-dev \
+    libxslt-dev \
+    libzstd-dev \
+    linux-perf \
+    locales \
+    lsb-release \
+    lsof \
+    make \
+    man \
+    meson \
+    neovim \
+    ninja-build \
+    perl \
+    pkg-config \
+    procps \
+    protobuf-c-compiler \
+    pspg \
+    python3 python3-pip \
+    strace \
+    sudo \
+    tmux \
+    tree \
+    unzip \
+    uuid-dev \
+    valgrind \
+    wget \
+    zlib1g-dev \
+ && apt remove libpq-dev -y \
+ && apt autoremove -y \
+ && apt clean
+
+RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
+ENV LANG en_US.UTF-8
+ENV LANGUAGE en_US:en
+ENV LC_ALL en_US.UTF-8
+
+RUN cpanm install IPC::Run
+RUN cpanm https://cpan.metacpan.org/authors/id/S/SH/SHANCOCK/Perl-Tidy-20230309.tar.gz
+
+# Since gdb will run in the context of the root user when debugging, we need add
+# both .gdbinit and gdbpg.py script into root home
+# gdbpg.py is adapted from https://github.com/tvondra/gdbpg
+COPY --chown=root:root .gdbinit /root/
+COPY --chown=root:root gdbpg.py /root/
+
+# add the postgres user to sudoers and allow all sudoers to login without a password prompt
+RUN useradd -ms /bin/bash postgres \
+ && usermod -aG sudo postgres \
+ && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers
+
+WORKDIR /home/postgres
+USER postgres
+
+COPY --chown=postgres:postgres .psqlrc .
+
+RUN sudo mkdir -p /opt/freedom \
+ && sudo chown -R postgres:postgres /opt/freedom
+
+ENV PATH="/home/postgres/pgsql/bin:$PATH"
+ENV PGPORT=5432
+EXPOSE 5432
diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000000..f92d116bcf
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,46 @@
+{
+    "build": { 
+        "dockerfile": "Dockerfile"
+    },
+    "runArgs": [
+        "--ulimit=core=-1",
+        "--cap-add=SYS_ADMIN",
+        "--cap-add=SYS_PTRACE",
+        "--security-opt",
+        "seccomp=unconfined",
+        "--privileged"
+    ],
+    "mounts": [
+        // You have to make sure source directory is avaliable on your host file system
+        "source=${localEnv:HOME}/freedom,target=/opt/freedom,type=bind,consistency=cached"
+    ],
+    "workspaceMount": "source=${localWorkspaceFolder},target=/home/postgres/postgres,type=bind,consistency=cached",
+    "workspaceFolder": "/home/postgres/postgres",
+    "forwardPorts": [
+        5432
+    ],
+    "customizations": {
+        "vscode": {
+            "extensions": [
+                "eamodio.gitlens",
+                "github.vscode-github-actions",
+                "GitHub.copilot",
+                "ms-vscode.cpptools-extension-pack",
+                "ms-vscode.hexeditor",
+                "ms-vsliveshare.vsliveshare",
+                "rioj7.command-variable",
+                "74th.scrollkey"
+            ],
+            "settings": {
+                "files.exclude": {
+                    "**/*.o": true,
+                    "**/.deps/": true
+                },
+                "C_Cpp.default.cStandard": "c99",
+                "C_Cpp.default.cppStandard": "c++17",
+                "C_Cpp.default.browse.databaseFilename": "${workspaceFolder}/.vscode/.browse.c_cpp.db"
+            }
+        }
+    },
+    "postCreateCommand": "bash ./.devcontainer/postCreateCommand.sh"
+}
diff --git a/.devcontainer/gdbpg.py b/.devcontainer/gdbpg.py
new file mode 100644
index 0000000000..776570b2ce
--- /dev/null
+++ b/.devcontainer/gdbpg.py
@@ -0,0 +1,386 @@
+import gdb
+
+def format_plan_tree(tree, indent=0):
+	'formats a plan (sub)tree, with custom indentation'
+
+	# if the pointer is NULL, just return (null) string
+	if (str(tree) == '0x0'):
+		return '-> (NULL)'
+
+	# format all the important fields (similarly to EXPLAIN)
+	retval = '''
+-> %(type)s (cost=%(startup).3f...%(total).3f rows=%(rows)s width=%(width)s)
+\ttarget list:
+%(target)s
+\t%(left)s
+\t%(right)s''' % {
+			'type' : format_type(tree['type']),			# type of the Node
+			'startup' : float(tree['startup_cost']),	# startup cost
+			'total' : float(tree['total_cost']),		# total cost
+			'rows' : str(tree['plan_rows']),			# number of rows
+			'width' : str(tree['plan_width']),			# tuple width (no header)
+
+			# format target list
+			'target' : format_node_list(tree['targetlist'], 2, True),
+
+			# left subtree
+			'left' : format_plan_tree(tree['lefttree'], 0),
+
+			# right subtree
+			'right' : format_plan_tree(tree['righttree'], 0)
+		}
+
+	return add_indent(retval, indent+1)
+
+
+def format_type(t, indent=0):
+	'strip the leading T_ from the node type tag'
+
+	t = str(t)
+
+	if t.startswith('T_'):
+		t = t[2:]
+
+	return add_indent(t, indent)
+
+
+def format_int_list(lst, indent=0):
+	'format list containing integer values directly (not warapped in Node)'
+
+	# handle NULL pointer (for List we return NIL
+	if (str(lst) == '0x0'):
+		return '(NIL)'
+
+	# we'll collect the formatted items into a Python list
+	tlist = []
+	item = lst['head']
+
+	# walk the list until we reach the last item
+	while str(item) != '0x0':
+
+		# get item from the list and just grab 'int_value as int'
+		tlist.append(int(item['data']['int_value']))
+
+		# next item
+		item = item['next']
+
+	return add_indent(str(tlist), indent)
+
+
+def format_oid_list(lst, indent=0):
+	'format list containing Oid values directly (not warapped in Node)'
+
+	# handle NULL pointer (for List we return NIL)
+	if (str(lst) == '0x0'):
+		return '(NIL)'
+
+	# we'll collect the formatted items into a Python list
+	tlist = []
+	item = lst['head']
+
+	# walk the list until we reach the last item
+	while str(item) != '0x0':
+
+		# get item from the list and just grab 'oid_value as int'
+		tlist.append(int(item['data']['oid_value']))
+
+		# next item
+		item = item['next']
+
+	return add_indent(str(tlist), indent)
+
+
+def format_node_list(lst, indent=0, newline=False):
+	'format list containing Node values'
+
+	# handle NULL pointer (for List we return NIL)
+	if (str(lst) == '0x0'):
+		return '(NIL)'
+
+	# we'll collect the formatted items into a Python list
+	tlist = []
+	item = lst['head']
+
+	# walk the list until we reach the last item
+	while str(item) != '0x0':
+
+		# we assume the list contains Node instances, so grab a reference
+		# and cast it to (Node*)
+		node = cast(item['data']['ptr_value'], 'Node')
+
+		# append the formatted Node to the result list
+		tlist.append(format_node(node))
+
+		# next item
+		item = item['next']
+
+	retval = str(tlist)
+	if newline:
+		retval = "\n".join([str(t) for t in tlist])
+
+	return add_indent(retval, indent)
+
+
+def format_char(value):
+	'''convert the 'value' into a single-character string (ugly, maybe there's a better way'''
+
+	str_val = str(value.cast(gdb.lookup_type('char')))
+
+	# remove the quotes (start/end)
+	return str_val.split(' ')[1][1:-1]
+
+
+def format_relids(relids):
+	return '(not implemented)'
+
+
+def format_node_array(array, start_idx, length, indent=0):
+
+	items = []
+	for i in range(start_idx,start_idx + length - 1):
+		items.append(str(i) + " => " + format_node(array[i]))
+
+	return add_indent(("\n".join(items)), indent)
+
+
+def format_node(node, indent=0):
+	'format a single Node instance (only selected Node types supported)'
+
+	if str(node) == '0x0':
+		return add_indent('(NULL)', indent)
+
+	retval = '';
+	type_str = str(node['type'])
+
+	if is_a(node, 'TargetEntry'):
+
+		# we assume the list contains Node instances (probably safe for Plan fields)
+		node = cast(node, 'TargetEntry')
+
+		name_ptr = node['resname'].cast(gdb.lookup_type('char').pointer())
+		name = "(NULL)"
+		if str(name_ptr) != '0x0':
+			name = '"' + (name_ptr.string()) + '"'
+
+		retval = 'TargetEntry (resno=%(resno)s resname=%(name)s origtbl=%(tbl)s origcol=%(col)s junk=%(junk)s expr=[%(expr)s])' % {
+				'resno' : node['resno'],
+				'name' : name,
+				'tbl' : node['resorigtbl'],
+				'col' : node['resorigcol'],
+				'junk' : (int(node['resjunk']) == 1),
+				'expr' : format_node(node['expr'])
+			}
+
+	elif is_a(node, 'Var'):
+
+		# we assume the list contains Node instances (probably safe for Plan fields)
+		node = cast(node, 'Var')
+
+		retval = 'Var (varno=%(no)s varattno=%(attno)s levelsup=%(levelsup)s)' % {
+				'no' : node['varno'],
+				'attno' : node['varattno'],
+				'levelsup' : node['varlevelsup']
+			}
+
+	elif is_a(node, 'RangeTblRef'):
+
+		node = cast(node, 'RangeTblRef')
+
+		retval = 'RangeTblRef (rtindex=%d)' % (int(node['rtindex']),)
+
+	elif is_a(node, 'RelOptInfo'):
+
+		node = cast(node, 'RelOptInfo')
+
+		retval = 'RelOptInfo (kind=%(kind)s relids=%(relids)s rtekind=%(rtekind)s relid=%(relid)s rows=%(rows)s width=%(width)s fk=%(fk)s)' % {
+				'kind' : node['reloptkind'],
+				'rows' : node['rows'],
+				'width' : node['width'],
+				'relid' : node['relid'],
+				'relids' : format_relids(node['relids']),
+				'rtekind' : node['rtekind'],
+				'fk' : (int(node['has_fk_join']) == 1)
+			}
+
+	elif is_a(node, 'RangeTblEntry'):
+
+		node = cast(node, 'RangeTblEntry')
+
+		retval = 'RangeTblEntry (kind=%(rtekind)s relid=%(relid)s relkind=%(relkind)s)' % {
+				'relid' : node['relid'],
+				'rtekind' : node['rtekind'],
+				'relkind' : format_char(node['relkind'])
+			}
+
+	elif is_a(node, 'PlannerInfo'):
+
+		retval = format_planner_info(node)
+
+	elif is_a(node, 'PlannedStmt'):
+
+		retval = format_planned_stmt(node)
+
+	elif is_a(node, 'List'):
+
+		retval = format_node_list(node, 0, True)
+
+	elif is_a(node, 'Plan'):
+
+		retval = format_plan_tree(node)
+
+	elif is_a(node, 'RestrictInfo'):
+
+		node = cast(node, 'RestrictInfo')
+
+		retval = '''RestrictInfo (pushed_down=%(push_down)s can_join=%(can_join)s delayed=%(delayed)s)
+%(clause)s
+%(orclause)s''' % {
+			'clause' : format_node(node['clause'], 1),
+			'orclause' : format_node(node['orclause'], 1),
+			'push_down' : (int(node['is_pushed_down']) == 1),
+			'can_join' : (int(node['can_join']) == 1),
+			'delayed' : (int(node['outerjoin_delayed']) == 1)
+		}
+
+	elif is_a(node, 'OpExpr'):
+
+		node = cast(node, 'OpExpr')
+
+		retval = format_op_expr(node)
+
+	elif is_a(node, 'BoolExpr'):
+
+		node = cast(node, 'BoolExpr')
+
+		print node
+
+		retval = format_bool_expr(node)
+
+	else:
+		# default - just print the type name
+		retval = format_type(type_str)
+
+	return add_indent(str(retval), indent)
+
+
+def format_planner_info(info, indent=0):
+
+	# Query *parse;			/* the Query being planned */
+	# *glob;				/* global info for current planner run */
+	# Index	query_level;	/* 1 at the outermost Query */
+	# struct PlannerInfo *parent_root;	/* NULL at outermost Query */
+	# List	   *plan_params;	/* list of PlannerParamItems, see below */
+
+	retval = '''rel:
+%(rel)s
+rte:
+%(rte)s
+''' % {'rel' : format_node_array(info['simple_rel_array'], 1, int(info['simple_rel_array_size'])),
+	   'rte' : format_node_array(info['simple_rte_array'], 1, int(info['simple_rel_array_size']))}
+
+	return add_indent(retval, indent)
+
+
+def format_planned_stmt(plan, indent=0):
+
+	retval = '''          type: %(type)s
+      query ID: %(qid)s
+    param exec: %(nparam)s
+     returning: %(has_returning)s
+ modifying CTE: %(has_modify_cte)s
+   can set tag: %(can_set_tag)s
+     transient: %(transient)s
+  row security: %(row_security)s
+               
+     plan tree: %(tree)s
+   range table:
+%(rtable)s
+ relation OIDs: %(relation_oids)s
+   result rels: %(result_rels)s
+  utility stmt: %(util_stmt)s
+      subplans: %(subplans)s''' % {
+			'type' : plan['commandType'],
+			'qid' : plan['queryId'],
+			'nparam' : plan['nParamExec'],
+			'has_returning' : (int(plan['hasReturning']) == 1),
+			'has_modify_cte' : (int(plan['hasModifyingCTE']) == 1),
+			'can_set_tag' : (int(plan['canSetTag']) == 1),
+			'transient' : (int(plan['transientPlan']) == 1),
+			'row_security' : (int(plan['hasRowSecurity']) == 1),
+			'tree' : format_plan_tree(plan['planTree']),
+			'rtable' : format_node_list(plan['rtable'], 1, True),
+			'relation_oids' : format_oid_list(plan['relationOids']),
+			'result_rels' : format_int_list(plan['resultRelations']),
+			'util_stmt' : format_node(plan['utilityStmt']),
+			'subplans' : format_node_list(plan['subplans'], 1, True)
+		  }
+
+	return add_indent(retval, indent)
+
+def format_op_expr(node, indent=0):
+
+	return """OpExpr [opno=%(opno)s]
+%(clauses)s""" % {	'opno' : node['opno'],
+					'clauses' : format_node_list(node['args'], 1, True)}
+
+def format_bool_expr(node, indent=0):
+
+	return """BoolExpr [op=%(op)s]
+%(clauses)s""" % {	'op' : node['boolop'],
+					'clauses' : format_node_list(node['args'], 1, True)}
+
+def is_a(n, t):
+	'''checks that the node has type 't' (just like IsA() macro)'''
+
+	if not is_node(n):
+		return False
+
+	return (str(n['type']) == ('T_' + t))
+
+
+def is_node(l):
+	'''return True if the value looks like a Node (has 'type' field)'''
+
+	try:
+		x = l['type']
+		return True
+	except:
+		return False
+
+def cast(node, type_name):
+	'''wrap the gdb cast to proper node type'''
+
+	# lookup the type with name 'type_name' and cast the node to it
+	t = gdb.lookup_type(type_name)
+	return node.cast(t.pointer())
+
+
+def add_indent(val, indent):
+
+	return "\n".join([(("\t"*indent) + l) for l in val.split("\n")])
+
+
+class PgPrintCommand(gdb.Command):
+	"print PostgreSQL structures"
+
+	def __init__ (self):
+		super (PgPrintCommand, self).__init__ ("pgprint",
+					gdb.COMMAND_SUPPORT,
+					gdb.COMPLETE_NONE, False)
+
+	def invoke (self, arg, from_tty):
+
+		arg_list = gdb.string_to_argv(arg)
+		if len(arg_list) != 1:
+			print "usage: pgprint var"
+			return
+
+		l = gdb.parse_and_eval(arg_list[0])
+
+		if not is_node(l):
+			print "not a node type"
+
+		print format_node(l)
+
+
+PgPrintCommand()
diff --git a/.devcontainer/launch.json b/.devcontainer/launch.json
new file mode 100644
index 0000000000..a3c0ae98fc
--- /dev/null
+++ b/.devcontainer/launch.json
@@ -0,0 +1,20 @@
+{
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "name": "(gdb) Attach",
+            "type": "cppdbg",
+            "request": "attach",
+            "processId": "${command:pickProcess}",
+            "program": "/home/postgres/pgsql/bin/postgres",
+            "additionalSOLibSearchPath": "/home/postgres/pgsql/lib",
+            "setupCommands": [
+                {
+                    "text": "handle SIGUSR1 noprint nostop pass",
+                    "description": "let gdb not stop when SIGUSR1 is sent to process",
+                    "ignoreFailures": true
+                }
+            ]
+        }
+    ]
+}
diff --git a/.devcontainer/postCreateCommand.sh b/.devcontainer/postCreateCommand.sh
new file mode 100644
index 0000000000..ef84027fc4
--- /dev/null
+++ b/.devcontainer/postCreateCommand.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+
+function configure_coredumps {
+  sudo sh -c "echo 'core.%p.sig%s.%ts' > /proc/sys/kernel/core_pattern"
+}
+
+function configure_git {
+  git config --global user.email email@example.com
+  git config --global user.name "Your Name"
+  git config --global core.editor vim
+
+  # alias
+  git config --global alias.br "branch -vv"
+  git config --global alias.ci "commit -s"
+  git config --global alias.co checkout
+  git config --global alias.cob "checkout -b"
+  git config --global alias.cp cherry-pick
+  git config --global alias.dh "diff --no-prefix HEAD"
+  git config --global alias.ps push
+  git config --global alias.st status
+  git config --global alias.lg "log --graph --format='%C(auto)%h%C(reset) %C(dim white)%an%C(reset) %C(green)%ai%C(reset) %C(auto)%d%C(reset)%n   %s'"
+  git config --global alias.lg10 "log --graph --pretty=format:'%C(yellow)%h%C(auto)%d%Creset %s %C(white)- %an, %ar%Creset' -10"
+  git config --global alias.lg20 "log --graph --pretty=format:'%C(yellow)%h%C(auto)%d%Creset %s %C(white)- %an, %ar%Creset' -20"
+  git config --global alias.lg30 "log --graph --pretty=format:'%C(yellow)%h%C(auto)%d%Creset %s %C(white)- %an, %ar%Creset' -30"
+  git config --global alias.fp "format-patch --stdout --no-prefix"
+
+  git config --global --add safe.directory /home/postgres/postgres
+}
+
+function configure_vscode {
+  mkdir -p .vscode
+  cat <<EOL > ".vscode/c_cpp_properties.json"
+{
+    "configurations": [
+        {
+            "name": "Postgres Development Configuration",
+            "includePath": [
+                "\${workspaceFolder}/**",
+                "\${workspaceFolder}/src/include/",
+                "\${workspaceFolder}/../build/src/include/"
+            ],
+            "cStandard": "c99",
+            "configurationProvider": "ms-vscode.makefile-tools"
+        }
+    ],
+    "version": 4
+}
+EOL
+
+  # Copy the launch.json and tasks.json files if they don't exist
+  if [ ! -f .vscode/launch.json ]; then
+    cp .devcontainer/launch.json .vscode/
+  fi
+  if [ ! -f .vscode/tasks.json ]; then
+    cp .devcontainer/tasks.json .vscode/
+  fi
+}
+
+function configure_perf {
+  sudo sh -c "echo 0 > /proc/sys/kernel/kptr_restrict"
+  sudo su -c "echo -1 > /proc/sys/kernel/perf_event_paranoid"
+
+  if [ ! -d /opt/freedom/tools/FlameGraph ]; then
+    git clone https://github.com/brendangregg/FlameGraph.git /opt/freedom/tools/FlameGraph
+  fi
+}
+
+function configure_pg_plugins {
+  if [ ! -d /opt/freedom/extensions/pg_plugins ]; then
+    git clone https://github.com/michaelpq/pg_plugins.git /opt/freedom/extensions/pg_plugins
+  fi
+}
+
+function main {
+  mkdir -p /opt/freedom/extensions
+  mkdir -p /opt/freedom/patches
+  mkdir -p /opt/freedom/tools
+
+  configure_coredumps
+  configure_git
+  configure_perf
+  configure_pg_plugins
+  configure_vscode
+}
+
+main $@
diff --git a/.devcontainer/tasks.json b/.devcontainer/tasks.json
new file mode 100644
index 0000000000..9759b8ee4b
--- /dev/null
+++ b/.devcontainer/tasks.json
@@ -0,0 +1,219 @@
+{
+    // See https://go.microsoft.com/fwlink/?LinkId=733558
+    // for the documentation about the tasks.json format
+    // See PostgreSQL meson doc link https://wiki.postgresql.org/wiki/Meson
+    "version": "2.0.0",
+    "tasks": [
+        {
+            "label": "meson setup",
+            "type": "shell",
+            "command": "meson",
+            "args": [
+                "setup",
+                "../build",             // out of source tree
+                "--prefix=/home/postgres/pgsql",
+                "--buildtype=debug",    // default is debugoptimized
+                "-Dc_args=-fno-inline-functions -fno-omit-frame-pointer -DCOPY_PARSE_PLAN_TREES -DWRITE_READ_PARSE_PLAN_TREES -DRAW_EXPRESSION_COVERAGE_TEST -DENFORCE_REGRESSION_TEST_NAME_RESTRICTIONS",
+                "-Dcassert=true",
+                "-Dtap_tests=enabled",
+                "--werror"
+            ],
+            "problemMatcher": [],
+            "detail": "meson setup debug configuration"
+        },
+        {
+            "label": "meson setup release",
+            "type": "shell",
+            "command": "meson",
+            "args": [
+                "setup",
+                "../build",
+                "--prefix=/home/postgres/pgsql",
+                "--buildtype=release"
+            ],
+            "problemMatcher": [],
+            "detail": "meson setup release configuration"
+        },
+        {
+            "label": "clear build directory",
+            "type": "shell",
+            "command": "rm -rf ../build",
+            "problemMatcher": [],
+            "detail": "clear build directory"
+        },
+        {
+            "label": "ninja build",
+            "type": "shell",
+            "command": "ninja",
+            "args":[
+                "-C",
+                "../build",
+                "-j",
+                "4"
+            ],
+            "problemMatcher": [],
+            "detail": "build postgres"
+        },
+        {
+            "label": "regression tests",
+            "type": "shell",
+            "command": "meson test -C ../build -q --print-errorlogs --suite setup --suite regress",
+            "problemMatcher": [],
+            "detail": "run main regression tests"
+        },
+        {
+            "label": "check-world",
+            "type": "shell",
+            "command": "meson test -C ../build -q --print-errorlogs",
+            "problemMatcher": [],
+            "detail": "run all tests"
+        },
+        {
+            "label": "ninja install",
+            "type": "shell",
+            "command": "ninja install -C ../build",
+            "problemMatcher": [],
+            "detail": "install pgsql"
+        },
+        {
+            "label": "init cluster",
+            "type": "shell",
+            "command": "/home/postgres/pgsql/bin/initdb",
+            "args": [
+                "-D",
+                "/home/postgres/pgdata"
+            ],
+            "problemMatcher": [],
+            "detail": "init cluster using initdb"
+        },
+        {
+            "label": "start cluster",
+            "type": "shell",
+            "command": "/home/postgres/pgsql/bin/pg_ctl",
+            "args": [
+                "-D",
+                "/home/postgres/pgdata",
+                "-l",
+                "/home/postgres/pgdata/logfile",
+                "start"
+            ],
+            "problemMatcher": [],
+            "detail": "start db cluster"
+        },
+        {
+            "label": "restart cluster",
+            "type": "shell",
+            "command": "/home/postgres/pgsql/bin/pg_ctl",
+            "args": [
+                "-D",
+                "/home/postgres/pgdata",
+                "-l",
+                "/home/postgres/pgdata/logfile",
+                "restart"
+            ],
+            "problemMatcher": [],
+            "detail": "restart db cluster"
+        },
+        {
+            "label": "stop cluster",
+            "type": "shell",
+            "command": "/home/postgres/pgsql/bin/pg_ctl",
+            "args": [
+                "-D",
+                "/home/postgres/pgdata",
+                "stop"
+            ],
+            "problemMatcher": [],
+            "detail": "stop db cluster"
+        },
+        {
+            "label": "clear pgdata",
+            "type": "shell",
+            "command": "rm -rf /home/postgres/pgdata",
+            "problemMatcher": [],
+            "detail": "clear pgdata directory"
+        },
+        {
+            "label": "install pg_bsd_indent",
+            "type": "shell",
+            "command": "sudo cp ../build/src/tools/pg_bsd_indent/pg_bsd_indent /usr/local/bin",
+            "problemMatcher": [],
+            "detail": "install pg_bsd_indent to /usr/local/bin"
+        },
+        {
+            "label": "pgindent",
+            "type": "shell",
+            "command": "src/tools/pgindent/pgindent ${input:selectDir}",
+            "problemMatcher": [],
+            "detail": "run pgindent on selected directory"
+        },
+        {
+            "label": "format patch",
+            "type": "shell",
+            "command": "git format-patch -o /opt/freedom/patches -${input:formatPatchNumber} -v ${input:formatPatchVersion}",
+            "problemMatcher": [],
+            "detail": "generate patches from the topmost <n> commits"
+        },
+        {
+            "label": "apply patch",
+            "type": "shell",
+            "command": "git am /opt/freedom/patches/${input:patchFile}",
+            "problemMatcher": [],
+            "detail": "apply patch"
+        },
+        {
+            "label": "generate flamegraph",
+            "type": "shell",
+            "command": "perf record -o ../perf_${input:processId}.data -g -F 99 -p ${input:processId} -- sleep 60 && perf script -i ../perf_${input:processId}.data | /opt/freedom/tools/FlameGraph/stackcollapse-perf.pl | /opt/freedom/tools/FlameGraph/flamegraph.pl > /opt/freedom/perf_${input:processId}.svg",
+            "problemMatcher": [],
+            "detail": "generate flamegraph"
+        }
+    ],
+    "inputs": [
+        {
+            "id": "selectDir",
+            "type": "command",
+            "command": "extension.commandvariable.pickStringRemember",
+            "args": {
+                "description": "Which directory to run for pgindent?",
+                "options": [
+                    ["Use previous directory", "${remember:srcSubDir}"],
+                    ["Pick directory", "${pickFile:srcSubDir}"]
+                ],
+                "default": null,
+                "pickFile": {
+                    "srcSubDir": {
+                        "description": "Which directory?",
+                        "include": "src/**",
+                        "showDirs": true,
+                        "keyRemember": "srcSubDir"
+                    }
+                }
+            }
+        },
+        {
+            "id": "formatPatchNumber",
+            "description": "patches from the topmost <n> commits",
+            "type": "promptString",
+            "default": "1"
+        },
+        {
+            "id": "formatPatchVersion",
+            "description": "<n>-th iteration of the topic",
+            "type": "promptString",
+            "default": "1"
+        },
+        {
+            "id": "patchFile",
+            "description": "Which patch file to apply?",
+            "type": "promptString",
+            "default": "*"
+        },
+        {
+            "id": "processId",
+            "description": "Which process to perf?",
+            "type": "promptString",
+            "default": ""
+        }
+    ]
+}
diff --git a/.gitignore b/.gitignore
index 4e911395fe..952e4c93ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,7 @@ win32ver.rc
 *.exe
 lib*dll.def
 lib*.pc
+.DS_Store
 
 # Local excludes in root directory
 /GNUmakefile
@@ -43,3 +44,5 @@ lib*.pc
 /Release/
 /tmp_install/
 /portlock/
+/.devcontainer/
+/.vscode/
-- 
2.39.2

