<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="http://gurjeet.singh.im/feed.xml" rel="self" type="application/atom+xml" /><link href="http://gurjeet.singh.im/" rel="alternate" type="text/html" /><updated>2026-04-26T08:06:50+00:00</updated><id>http://gurjeet.singh.im/feed.xml</id><title type="html">Gurjeet Singh</title><subtitle>Gurjeet Singh&apos;s personal weblog.</subtitle><entry><title type="html">Cisco Anyconnect VPN automation with TouchID on macOS</title><link href="http://gurjeet.singh.im/blog/cisco-anyconnect-vpn-automation-with-touchid-on-macos" rel="alternate" type="text/html" title="Cisco Anyconnect VPN automation with TouchID on macOS" /><published>2024-06-14T03:35:24+00:00</published><updated>2024-06-14T03:35:24+00:00</updated><id>http://gurjeet.singh.im/blog/cisco-anyconnect-vpn-automation-with-touchid-on-macos</id><content type="html" xml:base="http://gurjeet.singh.im/blog/cisco-anyconnect-vpn-automation-with-touchid-on-macos"><![CDATA[<h1 id="automate-cisco-anyconnect-vpn-reconnection">Automate Cisco AnyConnect VPN Reconnection</h1>

<p>Ideally any software like Cisco AnyConnect should support TouchID on macOS, so
as to make it conveninent for users to use these security tools. Unfortunately,
AnyConnect does not yet support TouchID.</p>

<p>Below are the shell functions I have written to keep my VPN connection always
connected. In a terminal window, I run the function <code class="language-plaintext highlighter-rouge">vpn_keep_connected</code> and it
polls the status of the VPN every few seconds. When it detects a disconnection
(perhaps because the laptop is waking up from sleep), it reinitiates the VPN
connection, and instead of having to type in my looong password, I just use
TouchID to login.</p>

<p>Please note that this intgrates with, and depends on, the TouchID based password
management setup I documented in my previous blog <a href="./passwordstore+gnupg+touchid">post</a>. This combination
ensures that you will not be prompted for the VPN password; instead, you will be
prompted for TouchID veification, and if that succeeds the VPN password will be
retrieved from the <a href="https://www.passwordstore.org/">PasswordStore</a>.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="k">function </span>vpn_connect<span class="o">()</span>
<span class="o">{</span>
  <span class="nb">local </span><span class="nv">user</span><span class="o">=</span><span class="s2">"userid"</span><span class="p">;</span>
  <span class="nb">local </span><span class="nv">password</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>pass show vpn_password<span class="si">)</span><span class="s2">"</span><span class="p">;</span>
  <span class="c"># Connect to VPN using CLI</span>
  <span class="nb">printf</span> <span class="s2">"</span><span class="nv">$user</span><span class="se">\n</span><span class="nv">$password</span><span class="se">\n</span><span class="s2">"</span> | /opt/cisco/secureclient/bin/vpn <span class="nt">-s</span> connect myaccess.oraclevpn.com
  <span class="c"># Start the App, so that its icon shows in the macOS menu bar to represent connection status</span>
  open /Applications/Cisco/Cisco<span class="se">\ </span>Secure<span class="se">\ </span>Client.app/
<span class="o">}</span>
 
<span class="k">function </span>vpn_disconnect<span class="o">()</span>
<span class="o">{</span>
  <span class="c"># Disconnect VPN client, if already connected</span>
  /opt/cisco/secureclient/bin/vpn disconnect
  <span class="c"># Kill the App, if running</span>
  <span class="nb">kill</span> <span class="si">$(</span>ps ax | <span class="nb">grep</span> <span class="nt">-e</span> <span class="s1">'[C]isco Secure Client'</span> | <span class="nb">cut</span> <span class="nt">-f</span> 1 <span class="nt">-d</span>?<span class="si">)</span>
<span class="o">}</span>
 
<span class="k">function </span>vpn_reconnect<span class="o">()</span>
<span class="o">{</span>
  vpn_disconnect
  vpn_connect
<span class="o">}</span>
 
<span class="k">function </span>vpn_is_disconnected<span class="o">()</span>
<span class="o">{</span>
  /opt/cisco/secureclient/bin/vpn status | <span class="nb">grep</span> <span class="nt">-q</span> <span class="s1">'state: Disconnected'</span><span class="p">;</span>
  <span class="k">return</span> <span class="nv">$?</span><span class="p">;</span>
<span class="o">}</span>
 
<span class="k">function </span>vpn_keep_connected<span class="o">()</span>
<span class="o">{</span>
  <span class="k">if </span>vpn_is_disconnected<span class="p">;</span> <span class="k">then
    </span><span class="nb">echo</span> <span class="s2">"INFO: VPN is disconnected. Reinitiating connection."</span>
    vpn_reconnect
  <span class="k">fi

  while </span><span class="nb">true</span><span class="p">;</span> <span class="k">do
    </span><span class="nb">echo </span>INFO: Waiting <span class="k">until </span>VPN is disconnected.
    <span class="k">while</span> <span class="o">!</span> vpn_is_disconnected<span class="p">;</span> <span class="k">do
      </span><span class="nb">sleep </span>5
    <span class="k">done
    </span><span class="nb">echo</span> <span class="s2">"INFO: VPN is disconnected. Reinitiating connection."</span>
    vpn_reconnect
  <span class="k">done</span>
<span class="o">}</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="blog" /><category term="pass," /><category term="password" /><category term="store," /><category term="touchid," /><category term="VPN," /><category term="encryption," /><category term="automation" /><summary type="html"><![CDATA[Automate Cisco AnyConnect VPN Reconnection]]></summary></entry><entry><title type="html">PasswordStore + GnuPG + TouchID</title><link href="http://gurjeet.singh.im/blog/passwordstore+gnupg+touchid" rel="alternate" type="text/html" title="PasswordStore + GnuPG + TouchID" /><published>2024-06-09T20:33:22+00:00</published><updated>2024-06-09T20:33:22+00:00</updated><id>http://gurjeet.singh.im/blog/passwordstore+gnupg+touchid</id><content type="html" xml:base="http://gurjeet.singh.im/blog/passwordstore+gnupg+touchid"><![CDATA[<h1 id="local-secure-convenient-shareable-password-management-with-gpg-git-and-touchid">Local, Secure, Convenient, Shareable Password Management with GPG, Git, and TouchID</h1>

<p>Open Source security tools abound, but common people don’t use them becuase
these tools cause inconvenience. If you give an average person a choice between
security and conveninence, they’ll always end up choosing convenience.</p>

<p>If you have done the right thing and set a strong password to protect your GPG
keys, unlocking GPG keys requires entering the (loooo…ng) password every time
you want to unlock the private key. And because that is very inconvenient, an
average person will choose to not use GPG keys. I am a prime example of this;
everytime I tried to use GPG for something in past, after dedicating significant
time to setting it up, I used to end up not using it because of the complexity,
inconvenience, and never being sure if I’m doing the right thing in terms of
security posture.</p>

<p>We’ll see how one can store their passwords locally, in encrypted form, and
protect those passwords with a GPG key. And to make it all extremely convenient
to use, the password used to protect the GPG private key is protected by a mere
touch of TouchID, so you won’t have to tediously type your long passwords.</p>

<p>Below I will show how to use <a href="https://www.passwordstore.org/">PasswordStore</a> <a href="https://en.wikipedia.org/wiki/Command-line_interface">CLI</a> (a.k.a <code class="language-plaintext highlighter-rouge">pass</code>) to
securely store all your passwords locally, and protect them with TouchID. In
essence, your TouchID will protect the GPG key passwords (stored in the macOS
login Keychain), which in turn will protect the GPG private key, which in turn
will protect the passwords stored in PasswordStore.</p>

<p>My use case was that I wanted to automate connection and reconnection of my VPN,
but did not want to be bothered to provide the long cryptic password every time.
Upon searching for TouchID-based soutions, I came across this <a href="https://blog.urbansedlar.com/archives/49880">blog</a> post
which mentioned that <code class="language-plaintext highlighter-rouge">pinentry-touchid</code> can be used for this purpose, but the
post did not have much details on installation and configuration of the tools. I
spent significant amount of time in understanding and configuring these
utilities, so I thought of documenting the precise instructions for others to
use.</p>

<p>With the implementation shown below, now whenever my scripts want to access the
VPN password, or any other secret stored in my PasswordStore, I get prompted
with the TouchID to unlock their access. As an added bonus, if you choose to,
the encrypted passwords can be managed in, and shared using <a href="https://git-scm.com/">Git</a>.</p>

<p>Hopefully the below instructions and explanation will make it convenient for
you to make use of GPG and PasswordStore.</p>

<p><strong><em>Update</em></strong> (2024/06/13):
I now use this setup to automatically reconnect my VPN connection, if it ever
gets disconnected. Please see this blog <a href="./cisco-anyconnect-vpn-automation-with-touchid-on-macos">post</a> for the shell functions I wrote
for this automation.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># Install the packages using Homebrew</span>
brew <span class="nb">install </span>gnupg
brew <span class="nb">install </span>pinentry
brew <span class="nb">install </span>pinentry-mac
brew <span class="nb">install </span>pass
brew tap jorgelbg/tap
brew <span class="nb">install </span>pinentry-touchid

<span class="c"># Perform a check to see if `pinentry-mac` is the default pinentry</span>
<span class="c"># mechanism.</span>
pinentry-touchid <span class="nt">-check</span>

<span class="c"># Expect to see a failure output as shown below.</span>
<span class="c">#</span>
<span class="c"># ❌ /opt/homebrew/opt/pinentry/bin/pinentry is a symlink that resolves to</span>
<span class="c"># /opt/homebrew/Cellar/pinentry/1.3.0/bin/pinentry-curses not to pinentry-mac</span>

<span class="c"># By default, `pinentry` is a link to `pinentry-ncurses`. The following command</span>
<span class="c"># will make the link point to `pinentry-mac`. Thereafter, `pinentry-mac` will</span>
<span class="c"># become the default program that gpg-agent will use to unlock the GPG keys.</span>
<span class="c"># </span>
<span class="c"># If you ever _reinstall_ gpg package, the link will be reset back to point to</span>
<span class="c"># `pinentry-ncurses`. You should run `pinentry-touchid -check` to see if it</span>
<span class="c"># succeeds, and run it with `-fix` option, like below, to fix the link, if</span>
<span class="c"># needed.</span>
pinentry-touchid <span class="nt">-fix</span>

<span class="c"># Perform the check again. This time it should pass.</span>
pinentry-touchid <span class="nt">-check</span>

<span class="c"># Generate a new private+public keypair.</span>
<span class="c">#</span>
<span class="c"># If unsure of which of the choices to pick, accept the defaults indicated in</span>
<span class="c"># the options shown by gpg. When asked for a password to ptorect the private</span>
<span class="c"># key, use a very strong password; the private key being generated will protect</span>
<span class="c"># all your passwords, so make sure it's not easy to guess the password that</span>
<span class="c"># protects it.</span>
<span class="c">#</span>
<span class="c"># To fully understand how to manage and backup your GPG keys, please read the</span>
<span class="c"># 'GPG Keys Management' section in Fedora documentation linked below.</span>
<span class="c">#</span>
<span class="c"># https://docs.fedoraproject.org/en-US/quick-docs/create-gpg-keys/</span>
<span class="c"># </span>
<span class="c"># If you already have a private+public keypair, you can use that instead of</span>
<span class="c"># creating a new one. In that case, skip this command.</span>
gpg <span class="nt">--full-generate-key</span>

<span class="c"># Note the KeyID of the key you created above, or the one you want to use. For</span>
<span class="c"># example, in the following output, 0xDA0564F36822B15B on the first line is the</span>
<span class="c"># KeyID.</span>
<span class="c">#</span>
<span class="c"># pub   ed25519/0xDA0564F36822B15B 2024-06-08 [SC] [expires: 2025-06-08]</span>
<span class="c">#       Key fingerprint = FFD9 2569 B7A2 D3B5 9EAD  4BC4 DA05 64F3 6822 B15B</span>
<span class="c"># uid                   [ultimate] John Doe (GPG key for Work) &lt;john.doe@expmple.com&gt;</span>
<span class="c"># sub   cv25519/0xB08D23EF71423D97 2024-06-08 [E] [expires: 2025-06-08]</span>
gpg <span class="nt">--list-keys</span> <span class="nt">--fingerprint</span> <span class="nt">--keyid-format</span> 0xlong

<span class="c"># Initialize PasswordStore's storage, `~/.passsword-store/`, replacing $KEYID</span>
<span class="c"># with the KeyID of the key you generated or chose above.</span>
pass init <span class="nv">$KEY_ID</span>

<span class="c"># (Optional) Have PasswordStore track and log password changes in a Git</span>
<span class="c"># repository. If you choose to do this, every addition or removal of a password</span>
<span class="c"># in the PasswordStore will create a Git commit in the Git repository stored at</span>
<span class="c"># `~/.password-store/`.</span>
pass git init

<span class="c"># Tell `pinentry-mac` (a product of GPGTools) to not store the passwords it</span>
<span class="c"># decrypts in the Keychain.</span>
<span class="c">#</span>
<span class="c"># Instead, `pinentry-touchid` will perform the storage operations on the</span>
<span class="c"># Keychain, hence it will be treated as the owner of the values it stores in</span>
<span class="c"># Keychain.  Which means `pinentry-touchid` can then read these values from</span>
<span class="c"># Keychain without triggering a macOS password prompt to the user.</span>
defaults write org.gpgtools.common DisableKeychain <span class="nt">-bool</span> <span class="nb">yes</span>

<span class="c"># Set `pinentry-touchid` as the default pinentry-program in `gpg-agent`'s config</span>
<span class="c"># file.</span>
<span class="nb">echo</span> <span class="s2">"pinentry-program </span><span class="si">$(</span>which pinentry-touchid<span class="si">)</span><span class="s2">"</span> <span class="o">&gt;&gt;</span> ~/.gnupg/gpg-agent.conf

<span class="c"># Tell `gpg-agent` to reload the config file changes made above.</span>
gpg-connect-agent reloadagent /bye

<span class="c"># Check that encryption works.</span>
<span class="c">#</span>
<span class="c"># You will _not_ be prompted for the password to unlock the GPG private key.</span>
<span class="c"># Encryption of the secrets/passwords does not require private key. This step</span>
<span class="c"># uses your public key, which is not protected, to perform the encryption.</span>
<span class="c">#</span>
<span class="c"># Create a test password and store it in PasswordStore. </span>
pass insert VPNpassword

<span class="c"># Retrieve the value of VPNpassword. You will now be prompted to provide the</span>
<span class="c"># password to unlock the private key.</span>
<span class="c">#</span>
<span class="c"># This should display a dialog box (created by `pinentry-touchid`) asking for</span>
<span class="c"># the password to unlock your GPG private key; provide the password you used in</span>
<span class="c"># `gpg --full-generate-key` step.</span>
<span class="c"># </span>
<span class="c"># If your provided password successfully unlocks the private key, then your</span>
<span class="c"># password will be stored in the macOS' Keychain. To verify this, in the</span>
<span class="c"># 'KeyChain Access' app, under 'login' Keychain, search for 'GnuPG'.</span>
<span class="c"># </span>
<span class="c"># Because this entry in the login Kaychain is created by the `pinentry-touchid`</span>
<span class="c"># program, `pinentry-touchid` is always allowed to read its value. To verify</span>
<span class="c"># this, open 'Keychain Access' app, search for 'GnuPG', right-click on the</span>
<span class="c"># password entry shown in results, then click on 'Get Info', and then on</span>
<span class="c"># 'Access Control'. You should see `pinentry-touchid` listed in the list under</span>
<span class="c"># 'Always allow access by these applicatons'.</span>
pass show VPNpassword

<span class="c"># The private key password has now been stored in Keychain by TouchID.</span>

<span class="c"># If you try to `show` this password again, you will _not_ be prompted to unlock</span>
<span class="c"># the PGP key, because it was cached by `gpg-agent` after the command above.</span>
pass show VPNpassword

<span class="c"># The private key password, after it has been retrieved from Keychain, is cached</span>
<span class="c"># by `gpg-agent`. So, usually you won't be  prompted to unlock the private key</span>
<span class="c"># for the next few minutes.</span>
<span class="c">#</span>
<span class="c"># But since we want to see the ultimate benefit, that of using TouchID to fetch</span>
<span class="c"># the private key password, we will force the `gpg-agent` to drop its cached</span>
<span class="c"># values of private key passwords.</span>
<span class="c">#</span>
<span class="c"># Tell `gpg-agent` to reload config (even though the config hasn't changed), and</span>
<span class="c"># it will additionally forget all cached private keys.</span>
gpg-connect-agent reloadagent /bye

<span class="c"># After dropping `gpg-agent`'s cached passwords, trying to `show` any password</span>
<span class="c"># in PasswordStore should prompt for the TouchID dialog. You will _not_ be</span>
<span class="c"># prompted for GPG private key password; the GPG private key password is now</span>
<span class="c"># stored in the login Keychain, and protected by your TouchID, thanks to</span>
<span class="c"># `pinentry-touchid`.</span>
pass show VPNpassword

<span class="c"># Since this was just for demo purposes, delete the `VPNpassword`.</span>
pass <span class="nb">rm </span>VPNpassword

<span class="c"># In essence, now your TouchID protects the GPG key passwords stored in the</span>
<span class="c"># macOS login Keychain, which in turn protect the GPG private keys, which in</span>
<span class="c"># turn protect the passwords stored in PasswordStore.</span>

<span class="c"># If you performed the optional `pass git init` command shown above, all changes</span>
<span class="c"># to the PasswordStore are now tracked in Git. This Git repository can now be</span>
<span class="c"># managed and shared just like any other Git repository.</span>
<span class="c">#</span>
<span class="c"># See the log of changes done to the PasswordStore.</span>
<span class="o">(</span> <span class="nb">cd</span> ~/.password-store/<span class="p">;</span> git log <span class="o">)</span>

<span class="c"># You should see output similar to the one below.</span>
<span class="c">#</span>
<span class="c"># commit 89fca136b832a0a829a0ee8f04197cc7147cb12d</span>
<span class="c"># Author: Gurjeet Singh &lt;gurjeet@singh.im&gt;</span>
<span class="c"># Date:   Sun Jun 9 21:17:33 2024 -0700</span>
<span class="c"># </span>
<span class="c">#     Remove VPNpassword from store.</span>
<span class="c"># </span>
<span class="c"># commit 41e950a6284e8f423a3f634d762038b77ff25df7</span>
<span class="c"># Author: Gurjeet Singh &lt;gurjeet@singh.im&gt;</span>
<span class="c"># Date:   Sun Jun 9 21:16:56 2024 -0700</span>
<span class="c"># </span>
<span class="c">#     Add given password for VPNpassword to store.</span>
<span class="c"># </span>
<span class="c"># commit 583e38474f3c341e648df666fce7b039712cb1cd</span>
<span class="c"># Author: Gurjeet Singh &lt;gurjeet@singh.im&gt;</span>
<span class="c"># Date:   Sun Jun 9 21:16:39 2024 -0700</span>
<span class="c"># </span>
<span class="c">#     Configure git repository for gpg file diff.</span>
<span class="c"># </span>
<span class="c"># commit a34b400171220fab8e3543afd01a917b38dc5da9</span>
<span class="c"># Author: Gurjeet Singh &lt;gurjeet@singh.im&gt;</span>
<span class="c"># Date:   Sun Jun 9 21:16:39 2024 -0700</span>
<span class="c"># </span>
<span class="c">#     Add current contents of password store.</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="blog" /><category term="pass," /><category term="password" /><category term="store," /><category term="touchid," /><category term="VPN," /><category term="encryption" /><summary type="html"><![CDATA[Local, Secure, Convenient, Shareable Password Management with GPG, Git, and TouchID]]></summary></entry><entry><title type="html">My Investigation into Rosetta Institute of Biomedical Research</title><link href="http://gurjeet.singh.im/blog/my-investigation-into-rosetta-institute-of-biomedical-research" rel="alternate" type="text/html" title="My Investigation into Rosetta Institute of Biomedical Research" /><published>2024-05-27T03:20:35+00:00</published><updated>2024-05-27T03:20:35+00:00</updated><id>http://gurjeet.singh.im/blog/my-investigation-into-rosetta-institute-of-biomedical-research</id><content type="html" xml:base="http://gurjeet.singh.im/blog/my-investigation-into-rosetta-institute-of-biomedical-research"><![CDATA[<!--
In this HTML comment is the voice transcription (using Google Docs' "Voice
typing" feature); these were 2 paragraphs, without any linebreaks; I inserted
the linebreaks when creating this post. Then follows the outside of this comment
is the polished up blog I generated using ChatGPT, on 2024/05/26. That's why the
filename purposely contains today's date, when I'm finally writing this text,
and the `date` field in the header above contains the actual date. Looking back
at the ChatGPT UI, apparently I was using the recently released GPT-4o.

This is the result of research that I've done on Rosetta Institute of Biomedical
Research. Their website is Rosetta institute.org all the research that I've done
so far doesn't yield any physical address linked to them and the ones that I
could find they seem to be not present at that address so this they advertise on
their website that they partner with UC Berkeley UC San Diego Columbia
University and Imperial College of London to offer summer 2 weeks program for
students students in high school and for the in-person courses at currently
their charging around 3,500 

We almost signed up for the summer program we were about to make a payment and
just before making the payment I thought of checking a few different things
about the the courses and the company itself so everything else looks good
testimony of peoples kind of photographs from 2013 or so everything checks out
but one thing that was curious was that they do not have an address on their
website they simply list Alameda California as this location which is very
generic so I went to Berkeley website berkeley.edu and search up their name
Rosetta Institute couldn't find anything relevant there I use Google search to
search berkeley.edu specifically and even that result did not those results like
they weren't any results for Rosetta Institute mentioned on berkeley.edu website
the email that we received for making the payment says that make a check payable
to Rosetta Institute of Medical Research at a certain address in Alameda but
when I use that address to look up that address that location on Google Maps
doesn't show any name anything related to Rosetta Institute so I used the street
view on Google Maps and see if there are any kind of office significant a fixed
it but nothing turned up even on Google Street View it's a very blank building
black door at that address some more Google search showed that there are various
websites that are dedicated to like helping students find summer programs they
all list Rosetta Institute as one of the options and again the same wordings
around we offer rising and exceptional high school students of course in
biotechnology of biomedical research but none of those websites actually linked
or showed any address of this this business or this company and some of the
results now started showing that this is listed or linked to a person named Ryan
Holzer I believe and up by the way this name is also mentioned in the website
and on the email address or sorry email signatures that that they are using to
correspond with us but by the way the email does not contain any specific
address to get to in in Berkeley has to be to show up if you're driving up to
them they say that this address will be shared at a later time pictures I can
kind of suspect that they may not have this thing kind of nail down or confirmed
yet so yeah on the link the one of the websites they kind of found the link
between Ryan Holzer and four businesses two of those businesses are now closed
and two are active the two businesses that are closed they were both named
Rosetta biomedical something so that's not a good sign I went to California
security of States website and searched for Rosetta Institute without the courts
and I got three results and all three results like they seem to be related to
this company but I cannot be sure because all of them have the phrase Institute
of biomedical something I'm in research and maybe there are a few other words
and all through those three businesses are closed the address that is in the
email they sent to send the check to at that address they were so many addresses
that there were so many businesses that were listed for like something to do
with tiles floorings some Pharmacy related businessBut nothing related to the
paramedical researchers at The Institute or anywhere close to that so yeah this
all tells me that this is kind of a fishy business and I don't feel comfortable
paying $3,500 for a 2 week program sending my daughter to a place who's
legitimacy I cannot confirm yeah it does look legitimate I looked up on LinkedIn
this Mr Brian George is listed on LinkedIn as well they have a Twitter link
Twitter page as well I think they have a Facebook page as well but I did not
look very closely at that and there are quite a few different people who have
listed their experience as a teaching assistant at Rosetta Institute and there
are a few students as well who have LinkedIn profiles who have listed there that
they have attended the residential program so program is legitimate it seems
like but but it's just that if there is no physical address if I can't find
these people's kind of credentials online clearly then it are not feeling I
don't feel comfortable sending my daughter to a 2 week program there moreover I
think Beyond just this kind of missing information lack of address this also
shows that they are not really a research institute they say they are
independently funded Research Institute if they are a research institute they
would have an office they haven't have a location that may have a lab Etc that
they're employees go to or their Partners or who is doing the research but if I
can't find that anywhere on Google Maps or on Google search results or
Berkeley's website or UC Berkeley's website or Columbia's website columbia.edu
as well so that raises the question that this is number one not associated with
these universities but these universities are probably leasing out space and
dormitories to this company but this company in itself is purely for this kind
of classes running these sessions and they don't really have a research
institute so yeah for that reason we are backing out hopefully this was helpful
for anybody trying to research them in the future 

ChatGPT prompt (on 2026/05/26): Create a blog post from the transcription of my
voice notes:<a blank line and then the above text, without linebreaks was given
to ChatGPT.>

Next prompt (on 2024/06/19): Produce the markdown for the above blog post you
generated.

I pasted the output, and then made the following changes:
1. Increased heading levels of all items by 2; removed 2 # symbols from every
   heading.
2. Removed the first line/heading, because the blog post title contains the same
   text.
3. Changed child => daughter
4. In the last sentence, Changed "your educational" => "your child's
   educational".
5. Inserted linebreaks; each paragraph here was originally on a sigle line.
6. Added section 'URLs', and Manually pasted the list of URLs from my research.
7. Changed the Blog title to remove prefix 'Unveiling the Truth: '; it felt like
   it was over the top. Also changed this file's name to remove that part from
filename.

Result follows:
-->

<p>In my quest to ensure the best educational opportunities for my daughter, I
recently embarked on a deep dive into the Rosetta Institute of Biomedical
Research. Their website, RosettaInstitute.org, promotes summer programs in
partnership with prestigious institutions like UC Berkeley, UC San Diego,
Columbia University, and Imperial College London. Despite the impressive
affiliations and promising courses for high school students, my research led me
to several concerning findings.</p>

<h2 id="the-lack-of-a-physical-address">The Lack of a Physical Address</h2>

<p>A significant red flag arose when I couldn’t find a physical address linked to
the Rosetta Institute. Their website vaguely lists Alameda, California, as their
location. However, this general reference didn’t satisfy my need for concrete
details. Determined to uncover more, I explored multiple avenues:</p>

<ol>
  <li><strong>Berkeley’s Website Search:</strong> A thorough search on Berkeley.edu yielded no
relevant results for the Rosetta Institute. Even a Google search specifically
targeting Berkeley’s site came up empty.</li>
  <li><strong>Google Maps Investigation:</strong> The address provided for sending payments in
Alameda didn’t show any indication of Rosetta Institute on Google Maps.
Street View revealed a nondescript building with a black door—hardly what one
would expect from a renowned research institute.</li>
</ol>

<h2 id="inconsistent-online-presence">Inconsistent Online Presence</h2>

<p>Several student-focused websites list the Rosetta Institute as a summer program
option, echoing the same marketing language found on Rosetta’s own site. Despite
the widespread promotion, none of these listings provided a physical address.
Furthermore, the name Ryan George appeared repeatedly, both on the institute’s
website and in email correspondence. However, a deeper dive into Ryan George’s
connections revealed some troubling details:</p>

<ol>
  <li><strong>Business Listings:</strong> Searches on the California Secretary of State’s
website showed three related results, all involving biomedical research, but
none confirmed a current, active entity. Additionally, previous businesses under
similar names were no longer operational.</li>
  <li><strong>Other Businesses at the Same Address:</strong> The provided address also housed
various unrelated businesses, like flooring companies and pharmacies, but
nothing connected to biomedical research.</li>
</ol>

<h2 id="questions-of-legitimacy">Questions of Legitimacy</h2>

<p>Despite some online presence on LinkedIn and other social media platforms, the
lack of a physical address and transparent credentials raised significant
doubts:</p>

<ol>
  <li><strong>LinkedIn and Social Media:</strong> Ryan George and the Rosetta Institute have
profiles on LinkedIn and Twitter. Several individuals list their roles as
teaching assistants, and some students note their attendance at the institute’s
programs. While this lends some credibility, it doesn’t fully dispel my
concerns.</li>
  <li><strong>Affiliations with Universities:</strong> My investigation suggests that the
universities mentioned might only be leasing space to the Rosetta Institute
for their summer programs, rather than being true partners in research. This
distinction is crucial, as it implies that the Rosetta Institute is more of an
educational service provider than a genuine research entity.</li>
</ol>

<h2 id="final-decision">Final Decision</h2>

<p>Given these findings, I am not comfortable proceeding with the $3,500 payment
for a two-week program at the Rosetta Institute. The combination of a vague
location, questionable business history, and lack of verifiable credentials
makes it too risky. While the program may be legitimate in terms of providing
educational experiences, the surrounding uncertainties are too significant to
ignore.</p>

<p>I hope this detailed account of my research and findings helps others who are
considering the Rosetta Institute of Biomedical Research. It’s crucial to
thoroughly vet educational programs, especially when they demand substantial
financial and personal commitments. Stay informed and prioritize transparency
and credibility to ensure the best outcomes for your child’s educational
investments.</p>

<p><strong><em>Disclosure</em></strong>: The above blog was generated using ChatGPT. To see my original
notes, see the HTML contents of this page.</p>

<h2 id="urls">URLs</h2>

<p>Below are most of the URLs/links of searches and webpages I looked at, during this research.</p>

<p>The address they shared for sending the check, the non-descript builiding, as
seen on Google maps Street View: 2177 Harbor Bay Pkwy, Alameda, CA, 94502
<a href="https://www.google.com/maps/place/2177+Harbor+Bay+Pkwy,+Alameda,+CA+94502/@37.7291249,-122.2471936,3a,75y,132.4h,94.88t/data=!3m6!1e1!3m4!1sApYwWM7MpTGf1jtBWdL1iw!2e0!7i16384!8i8192!4m15!1m8!3m7!1s0x808f846b5aae3a03:0x554afe95159bf16d!2s2177+Harbor+Bay+Pkwy,+Alameda,+CA+94502!3b1!8m2!3d37.7288816!4d-122.2472953!16s%2Fg%2F11dztqk4qg!3m5!1s0x808f846b5aae3a03:0x554afe95159bf16d!8m2!3d37.7288816!4d-122.2472953!16s%2Fg%2F11dztqk4qg?entry=ttu">https://www.google.com/maps/place/2177+Harbor+Bay+Pkwy,+Alameda,+CA+94502</a></p>

<p><a href="https://twitter.com/RosettaBio">https://twitter.com/RosettaBio</a></p>

<p><a href="https://x.com/RosettaBio">https://x.com/RosettaBio</a></p>

<p><a href="https://www.linkedin.com/search/results/all/?keywords=Rosetta%20Institute%20of%20Biomedical%20Research&amp;sid=A%40N">https://www.linkedin.com/search/results/all/?keywords=Rosetta%20Institute%20of%20Biomedical%20Research&amp;sid=A%40N</a></p>

<p><a href="https://www.linkedin.com/search/results/people/?keywords=rosetta%20institute&amp;origin=CLUSTER_EXPANSION&amp;page=2&amp;sid=X">https://www.linkedin.com/search/results/people/?keywords=rosetta%20institute&amp;origin=CLUSTER_EXPANSION&amp;page=2&amp;sid=X</a></p>

<p><a href="https://www.linkedin.com/in/ryan-g-holzer-85111144/">https://www.linkedin.com/in/ryan-g-holzer-85111144/</a></p>

<p><a href="https://www.google.com/search?q=site%3Aberkeley.edu+ryan+holzer">https://www.google.com/search?q=site%3Aberkeley.edu+ryan+holzer</a></p>

<p><a href="https://www.google.com/search?q=columbia+university+rosetta+institute">https://www.google.com/search?q=columbia+university+rosetta+institute</a></p>

<p><a href="https://www.rosettainstitute.org/biomedical-summer-camps/">https://www.rosettainstitute.org/biomedical-summer-camps/</a></p>

<p><a href="https://www.rosettainstitute.org/biomedical-summer-camps/curriculum/">https://www.rosettainstitute.org/biomedical-summer-camps/curriculum/</a></p>

<p><a href="https://www.rosettainstitute.org/biomedical-summer-camps/benefits-of-attendance/">https://www.rosettainstitute.org/biomedical-summer-camps/benefits-of-attendance/</a></p>

<p><a href="https://www.rosettainstitute.org/">https://www.rosettainstitute.org/</a></p>

<p><a href="https://www.forbes.com/sites/kristenmoon/2023/11/10/10-winter-camps-for-high-school-students/">https://www.forbes.com/sites/kristenmoon/2023/11/10/10-winter-camps-for-high-school-students/</a></p>

<p><a href="https://www.rosettainstitute.org/contact/">https://www.rosettainstitute.org/contact/</a></p>

<p><a href="https://www.rosettainstitute.org/biomedical-summer-camps/faq/">https://www.rosettainstitute.org/biomedical-summer-camps/faq/</a></p>

<p><a href="https://www.google.com/maps/search/rosetta+institute+of+biomedical+research">https://www.google.com/maps/search/rosetta+institute+of+biomedical+research</a></p>

<p><a href="https://www.berkeley.edu/search/?q=%22rosetta+institute%22">https://www.berkeley.edu/search/?q=%22rosetta+institute%22</a></p>

<p><a href="https://www.berkeley.edu/search/?q=rosetta+institute">https://www.berkeley.edu/search/?q=rosetta+institute</a></p>

<p><a href="https://www.google.com/search?q=site%3Aberkeley.edu+%22rosetta+institute%22">https://www.google.com/search?q=site%3Aberkeley.edu+%22rosetta+institute%22</a></p>

<p><a href="https://www.google.com/search?q=rosetta+institute+alameda+ca+-site%3Arosettainstitute.org">https://www.google.com/search?q=rosetta+institute+alameda+ca+-site%3Arosettainstitute.org</a></p>

<p><a href="https://www.google.com/search?q=%22rosetta+institute%22+-site%3Arosettainstitute.org">https://www.google.com/search?q=%22rosetta+institute%22+-site%3Arosettainstitute.org</a></p>

<p><a href="https://www.google.com/maps/place/2177+Harbor+Bay+Pkwy,+Alameda,+CA+94502/@37.7288816,-122.2472953,17z/">https://www.google.com/maps/place/2177+Harbor+Bay+Pkwy,+Alameda,+CA+94502/@37.7288816,-122.2472953,17z/</a></p>

<p><a href="https://www.google.com/search?q=%222177+Harbor+Bay+Pkwy%22">https://www.google.com/search?q=%222177+Harbor+Bay+Pkwy%22</a></p>

<p><a href="https://www.alamedaca.gov/files/assets/public/v/1/finance/current-businesses-february-2019.pdf">https://www.alamedaca.gov/files/assets/public/v/1/finance/current-businesses-february-2019.pdf</a></p>

<p><a href="https://www.corporationwiki.com/p/2ew92x/ryan-george-holzer">https://www.corporationwiki.com/p/2ew92x/ryan-george-holzer</a></p>

<p><a href="https://www.corporationwiki.com/p/2kssl6/rosetta-medical-research-institute-inc">https://www.corporationwiki.com/p/2kssl6/rosetta-medical-research-institute-inc</a></p>

<p><a href="https://www.summercamps.com/camp/rosetta-institute-molecular-medicine-summer-camps/">https://www.summercamps.com/camp/rosetta-institute-molecular-medicine-summer-camps/</a></p>

<p><a href="https://www.bbb.org/us/ca/alameda/profile/flooring-contractors/bay-area-flooring-superstore-1116-301108">https://www.bbb.org/us/ca/alameda/profile/flooring-contractors/bay-area-flooring-superstore-1116-301108</a></p>

<p><a href="https://www.yelp.com/biz/tata-enterprises-alameda">https://www.yelp.com/biz/tata-enterprises-alameda</a></p>

<p><a href="https://www.google.com/maps/place/1835+Queen+Elizabeth+Way,+San+Jose,+CA+95132/@37.4136029,-121.8533716,18z/">https://www.google.com/maps/place/1835+Queen+Elizabeth+Way,+San+Jose,+CA+95132/@37.4136029,-121.8533716,18z/</a></p>

<p><a href="https://www.google.com/search?q=california+list+of+registered+companies">https://www.google.com/search?q=california+list+of+registered+companies</a></p>

<p><a href="https://bizfileonline.sos.ca.gov/search/business">https://bizfileonline.sos.ca.gov/search/business</a></p>

<p><a href="https://www.activityhero.com/biz/rosetta-institute-of-biomedical-research">https://www.activityhero.com/biz/rosetta-institute-of-biomedical-research</a></p>

<p><a href="https://portolapilot.com/summer-programs-are-a-waste-of-time-and-money/">https://portolapilot.com/summer-programs-are-a-waste-of-time-and-money/</a></p>

<p><a href="https://talk.collegeconfidential.com/t/rosetta-institutes-cancer-neuroscience-camps/1685720/44">https://talk.collegeconfidential.com/t/rosetta-institutes-cancer-neuroscience-camps/1685720/44</a></p>

<p><a href="https://www.reddit.com/r/ApplyingToCollege/comments/hxvpep/is_rosetta_institute_of_biomedical_research_worth/">https://www.reddit.com/r/ApplyingToCollege/comments/hxvpep/is_rosetta_institute_of_biomedical_research_worth/</a></p>

<p><a href="https://www.berkeleyside.org/listings/items/rosetta-institute-of-biomedical-research-molecular-medicine-workshops">https://www.berkeleyside.org/listings/items/rosetta-institute-of-biomedical-research-molecular-medicine-workshops</a></p>]]></content><author><name></name></author><category term="blog" /><category term="Comma," /><category term="Separated," /><category term="Categories," /><category term="Go," /><category term="Here" /><summary type="html"><![CDATA[&lt;!– In this HTML comment is the voice transcription (using Google Docs’ “Voice typing” feature); these were 2 paragraphs, without any linebreaks; I inserted the linebreaks when creating this post. Then follows the outside of this comment is the polished up blog I generated using ChatGPT, on 2024/05/26. That’s why the filename purposely contains today’s date, when I’m finally writing this text, and the date field in the header above contains the actual date. Looking back at the ChatGPT UI, apparently I was using the recently released GPT-4o.]]></summary></entry><entry><title type="html">Postgres Ordering Data Type - A Failed Experiment</title><link href="http://gurjeet.singh.im/blog/postgres-ordering-data-type-a-failed-experiment" rel="alternate" type="text/html" title="Postgres Ordering Data Type - A Failed Experiment" /><published>2023-05-18T00:50:08+00:00</published><updated>2023-05-18T00:50:08+00:00</updated><id>http://gurjeet.singh.im/blog/postgres-ordering-data-type---a-failed-experiment</id><content type="html" xml:base="http://gurjeet.singh.im/blog/postgres-ordering-data-type-a-failed-experiment"><![CDATA[<!-- ChatGPT rephrased the blog as follows. The prompt was:

Rephrase the following blog post:

Postgres Ordering Data Type - A Failed Experiment

<rest of the body of the post, as copied from the rendered webpage, followed it.>

-->

<!-- hiding the proposed title, since I like mine better.
Failed Experiment: Postgres Data Type for Ordering Data

Other changes made after copying from ChatGPT's response:

Used `` to make some text look like code.

Reintroduced the hyperlinks.
-->

<p>I am sharing this unsuccessful experiment to embrace the spirit of acknowledging
and learning from our <a href="https://en.wikipedia.org/wiki/Michelson%E2%80%93Morley_experiment">failed attempts</a>. In a <a href="https://news.ycombinator.com/item?id=34565332">feature request</a> discussion on
HackerNews, a user named danielheath proposed the idea of a Postgres data type
that would allow assigning a specific position to an item/row in a table. The
expectation was that Postgres would automatically adjust the positions of other
items in the list to accommodate the assigned position.</p>

<p>An example scenario presented was managing a playlist, where a user wants to
move a song to a particular position, such as position 3. This feature would
relieve the programmer from the burden of manually rearranging the list and
allow assigning the desired position directly. When retrieving the results, the
item should appear at the assigned position.</p>

<p>Although several <a href="https://news.ycombinator.com/item?id=34565522">solutions</a> were proposed at the time, it was unclear if any of
them met the exact requirements of the request. I pondered over possible
implementations and eventually devised a method that I believed could achieve
the desired outcome. Motivated by this, I decided to code the solution using
plain SQL over the weekend, hoping that if successful, I could propose it as a
feature for inclusion in the Postgres project.</p>

<p>Below, you can find the code for the solution I developed. The fundamental idea
was to dynamically assign positions at runtime. When an update request arrived,
I used the requested position along with the current timestamp to assign a value
for sorting the results.</p>

<p>Initially, the solution seemed to work, but its limitations soon became
apparent. Towards the end, you can observe that the solution fails to set the
position of ‘Song B’ to 3 under certain conditions. The problem with this
approach, as well as any other potential solutions, is that we require a sorted
list of elements before we can place an item at a desired position. In an
attempt to address this, I partially implemented the <code class="language-plaintext highlighter-rouge">set_song_position_v2()</code>
function. However, even if this function were complete and handled all possible
edge cases, it would not fulfill the requested feature. A proper solution
necessitates working with a pre-sorted list, which contradicts the essence of
the feature request—to easily assign the desired position without additional
effort.</p>

<p>In simpler terms, this issue is analogous to trying to obtain the max() value of
a column without maintaining an index containing all the rows.</p>

<!-- The original text of the post follows, as I published it before rephrasing with
ChatGPT's help.

I'm publishing this failed experiment in the spirit of the [most famous failed
experiment][]. We should be more open about our failed attempts.

[most famous failed experiment]: https://en.wikipedia.org/wiki/Michelson%E2%80%93Morley_experiment

This started as a disucssion on HackerNews as a [feature request][], where
danielheath asked for a Postgres data type that, when used as a column in a
table, would allow the user to simply assign a desired position to an item/row
in the list, and the system (Postgres, in this case) would do the necessary
moving of all the items in the list to ensure that the item/row is assigned that
position in the list.

[feature request]: https://news.ycombinator.com/item?id=34565332

The concrete example they used was of managing a playlist, where the user wants
a song to be moved to position N, say 3. This feature would allow the
programmer/developer to not worry about rearranging the other items in the list,
and simply assign the desired position to the item. And when retrieving the
results, the item should show up at the assigned position.

At the time I [proposed a solution][], as did others, but it wasn't clear that
any of the proposed solutions were close to the actual request. I kept thinking
of ways to implement it, and a few days later I came up with a way to achieve
the desired effect; or at least I thought it was _the_ solution. So this last
weekend I attempted to code it up using plain SQL, with the hopes that if it
succeeded, I might be able to propose such a feature to be included in Postgres
project.

[proposed a solution]: https://news.ycombinator.com/item?id=34565522

Below you can see the code for the solution I had come up with. The core idea is
to assign actual positons at runtime, and when an update request arrives, use
the requested position, plus the current timestamp, to assgin a value that'll be
used to sort results.

The solution seems to work, but not for long. As you can see towards the end,
that this solution fails to set the position of the 'Song B' to 3 under one of
the conditions. The problem with this solution, and any others that may be
proposed, is that that we need a sorted list of elements before we can place an
item at some desired position in that list. You can see I tried to code up that
in `set_song_position_v2()` function at the end. The `_v2` function is not
complete, but even if it were complete and handled all the corner cases, I would
not call it a solution to the feature that was requested. The correct solution
has to use a _sorted_ list to begin with, and that is a non-starter, because the
feature request was to simply set the desired position, and not have to do any
work.

To state simply, it's the same problem as trying to get the `max()` value of a
column without trying to maintain an index that contains all the rows.

-->

<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*
 * The playlists table holds all the playlists, and the songs in those
 * playlists. This table is denormalized, and it does not have any indexes, to
 * keep the code simple and readable.
 *
 * The position_ts column records the timestamp when the operation occurred.
 * This column is then used to resolve conflicts when the song_position of 2
 * or more songs is the same.
 */</span>
<span class="k">create</span> <span class="k">table</span> <span class="n">playlists</span><span class="p">(</span>
    <span class="n">playlist_name</span> <span class="nb">text</span><span class="p">,</span>
    <span class="n">song_name</span> <span class="nb">text</span><span class="p">,</span>
    <span class="n">song_position</span> <span class="nb">integer</span><span class="p">,</span>
    <span class="n">position_ts</span> <span class="n">timestamptz</span> <span class="k">default</span> <span class="n">clock_timestamp</span><span class="p">()</span>
<span class="p">);</span>
<span class="o">&gt;</span> <span class="k">CREATE</span> <span class="k">TABLE</span>

<span class="k">create</span> <span class="k">or</span> <span class="k">replace</span> <span class="k">function</span> <span class="n">update_playlist_position_ts</span><span class="p">()</span>
    <span class="k">returns</span> <span class="k">trigger</span> <span class="k">as</span>
    <span class="err">$$</span>
    <span class="k">begin</span>
        <span class="k">new</span><span class="p">.</span><span class="n">position_ts</span> <span class="o">=</span> <span class="n">clock_timestamp</span><span class="p">();</span>
        <span class="k">return</span> <span class="k">new</span><span class="p">;</span>
    <span class="k">end</span><span class="p">;</span>
    <span class="err">$$</span> <span class="k">language</span> <span class="n">plpgsql</span><span class="p">;</span>
<span class="o">&gt;</span> <span class="k">CREATE</span> <span class="k">FUNCTION</span>

<span class="k">create</span> <span class="k">or</span> <span class="k">replace</span> <span class="k">trigger</span> <span class="n">update_position_ts</span>
    <span class="k">before</span> <span class="k">update</span> <span class="k">of</span> <span class="n">song_position</span> <span class="k">on</span> <span class="n">playlists</span>
    <span class="k">for</span> <span class="k">each</span> <span class="k">row</span>
    <span class="k">execute</span> <span class="k">function</span> <span class="n">update_playlist_position_ts</span><span class="p">();</span>
<span class="o">&gt;</span> <span class="k">CREATE</span> <span class="k">TRIGGER</span>

<span class="k">create</span> <span class="k">or</span> <span class="k">replace</span> <span class="k">view</span> <span class="n">playlists_numbered</span> <span class="k">as</span>
    <span class="k">select</span>  <span class="n">row_number</span><span class="p">()</span> <span class="n">over</span> <span class="p">(</span><span class="k">partition</span> <span class="k">by</span> <span class="n">playlist_name</span> <span class="k">order</span> <span class="k">by</span> <span class="n">song_position</span> <span class="k">asc</span><span class="p">,</span> <span class="n">position_ts</span> <span class="k">desc</span><span class="p">)</span> <span class="k">as</span> <span class="k">position</span><span class="p">,</span>
            <span class="n">playlist_name</span><span class="p">,</span>
            <span class="n">song_name</span>
    <span class="k">from</span>   <span class="p">(</span><span class="k">select</span>  <span class="n">playlist_name</span><span class="p">,</span>
                    <span class="n">song_name</span><span class="p">,</span>
                    <span class="n">song_position</span><span class="p">,</span>
                    <span class="n">position_ts</span>
            <span class="k">from</span>    <span class="n">playlists</span><span class="p">);</span>
<span class="o">&gt;</span> <span class="k">CREATE</span> <span class="k">VIEW</span>

<span class="c1">-- Simpler view definition with similar (but not the same) results as the</span>
<span class="c1">-- playlists_numbered view.</span>
<span class="k">create</span> <span class="k">or</span> <span class="k">replace</span> <span class="k">view</span> <span class="n">playlists_ordered</span> <span class="k">as</span>
    <span class="k">select</span>  <span class="n">row_number</span><span class="p">()</span> <span class="n">over</span> <span class="p">()</span> <span class="k">as</span> <span class="k">position</span><span class="p">,</span> <span class="o">*</span>
    <span class="k">from</span>   <span class="p">(</span><span class="k">select</span>  <span class="n">playlist_name</span><span class="p">,</span>
                    <span class="n">song_name</span>
            <span class="k">from</span>    <span class="n">playlists</span>
            <span class="k">order</span> <span class="k">by</span>
                    <span class="n">playlist_name</span><span class="p">,</span>
                    <span class="n">song_position</span> <span class="k">asc</span><span class="p">,</span>
                    <span class="n">position_ts</span> <span class="k">desc</span><span class="p">);</span>
<span class="o">&gt;</span> <span class="k">CREATE</span> <span class="k">VIEW</span>

<span class="c1">-- Set the position of a song within a playlist</span>
<span class="k">create</span> <span class="k">or</span> <span class="k">replace</span> <span class="k">function</span> <span class="n">set_song_position</span><span class="p">(</span><span class="n">playlist</span> <span class="nb">text</span><span class="p">,</span> <span class="n">song</span> <span class="nb">text</span><span class="p">,</span> <span class="n">new_position</span> <span class="nb">int</span><span class="p">)</span>
<span class="k">returns</span> <span class="n">void</span> <span class="k">as</span>
<span class="err">$$</span>
    <span class="c1">-- Simply set the 'song_positon' to the desired value; the</span>
    <span class="c1">-- trigger and the View will do the rest to actually show</span>
    <span class="c1">-- this song at a position relative to other songs within</span>
    <span class="c1">-- the playlist.</span>
    <span class="c1">--</span>
    <span class="c1">-- TODO: should it throw error if song is not already in the</span>
    <span class="c1">-- playlist?</span>
    <span class="k">update</span>      <span class="n">playlists</span>
        <span class="k">set</span>     <span class="n">song_position</span> <span class="o">=</span> <span class="err">$</span><span class="mi">3</span>
        <span class="k">where</span>   <span class="n">playlist_name</span> <span class="o">=</span> <span class="err">$</span><span class="mi">1</span>
        <span class="k">and</span>     <span class="n">song_name</span> <span class="o">=</span> <span class="err">$</span><span class="mi">2</span><span class="p">;</span>
<span class="err">$$</span>
<span class="k">language</span> <span class="k">sql</span><span class="p">;</span>
<span class="o">&gt;</span> <span class="k">CREATE</span> <span class="k">FUNCTION</span>

<span class="k">insert</span> <span class="k">into</span> <span class="n">playlists</span> <span class="k">values</span>
    <span class="p">(</span><span class="s1">'Playlist A'</span><span class="p">,</span> <span class="s1">'Song A'</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span>
    <span class="p">(</span><span class="s1">'Playlist A'</span><span class="p">,</span> <span class="s1">'Song B'</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span>
    <span class="p">(</span><span class="s1">'Playlist A'</span><span class="p">,</span> <span class="s1">'Song C'</span><span class="p">,</span> <span class="mi">3</span><span class="p">),</span>
    <span class="p">(</span><span class="s1">'Playlist B'</span><span class="p">,</span> <span class="s1">'Song X'</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span>
    <span class="p">(</span><span class="s1">'Playlist B'</span><span class="p">,</span> <span class="s1">'Song Y'</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="o">&gt;</span> <span class="k">INSERT</span> <span class="mi">0</span> <span class="mi">5</span>

<span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">playlists_numbered</span><span class="p">;</span>
<span class="o">&gt;</span>  <span class="k">position</span> <span class="o">|</span> <span class="n">playlist_name</span> <span class="o">|</span> <span class="n">song_name</span>
<span class="o">&gt;</span> <span class="c1">----------+---------------+-----------</span>
<span class="o">&gt;</span>         <span class="mi">1</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">A</span>
<span class="o">&gt;</span>         <span class="mi">2</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">B</span>
<span class="o">&gt;</span>         <span class="mi">3</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="k">C</span>
<span class="o">&gt;</span>         <span class="mi">1</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">B</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">X</span>
<span class="o">&gt;</span>         <span class="mi">2</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">B</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">Y</span>
<span class="o">&gt;</span> <span class="p">(</span><span class="mi">5</span> <span class="k">rows</span><span class="p">)</span>

<span class="k">select</span> <span class="n">set_song_position</span><span class="p">(</span><span class="s1">'Playlist A'</span><span class="p">,</span> <span class="s1">'Song C'</span><span class="p">,</span> <span class="mi">2</span><span class="p">);</span>
<span class="o">&gt;</span>  <span class="n">set_song_position</span>
<span class="o">&gt;</span> <span class="c1">-------------------</span>
<span class="o">&gt;</span> 
<span class="o">&gt;</span> <span class="p">(</span><span class="mi">1</span> <span class="k">row</span><span class="p">)</span>

<span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">playlists_numbered</span>
    <span class="k">where</span> <span class="n">playlist_name</span> <span class="o">=</span> <span class="s1">'Playlist A'</span><span class="p">;</span>
<span class="o">&gt;</span>  <span class="k">position</span> <span class="o">|</span> <span class="n">playlist_name</span> <span class="o">|</span> <span class="n">song_name</span>
<span class="o">&gt;</span> <span class="c1">----------+---------------+-----------</span>
<span class="o">&gt;</span>         <span class="mi">1</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">A</span>
<span class="o">&gt;</span>         <span class="mi">2</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="k">C</span>
<span class="o">&gt;</span>         <span class="mi">3</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">B</span>
<span class="o">&gt;</span> <span class="p">(</span><span class="mi">3</span> <span class="k">rows</span><span class="p">)</span>

<span class="k">select</span> <span class="n">set_song_position</span><span class="p">(</span><span class="s1">'Playlist A'</span><span class="p">,</span> <span class="s1">'Song B'</span><span class="p">,</span> <span class="mi">1</span><span class="p">);</span>
<span class="o">&gt;</span>  <span class="n">set_song_position</span>
<span class="o">&gt;</span> <span class="c1">-------------------</span>
<span class="o">&gt;</span> 
<span class="o">&gt;</span> <span class="p">(</span><span class="mi">1</span> <span class="k">row</span><span class="p">)</span>

<span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">playlists_numbered</span>
    <span class="k">where</span> <span class="n">playlist_name</span> <span class="o">=</span> <span class="s1">'Playlist A'</span><span class="p">;</span>
<span class="o">&gt;</span>  <span class="k">position</span> <span class="o">|</span> <span class="n">playlist_name</span> <span class="o">|</span> <span class="n">song_name</span>
<span class="o">&gt;</span> <span class="c1">----------+---------------+-----------</span>
<span class="o">&gt;</span>         <span class="mi">1</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">B</span>
<span class="o">&gt;</span>         <span class="mi">2</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">A</span>
<span class="o">&gt;</span>         <span class="mi">3</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="k">C</span>
<span class="o">&gt;</span> <span class="p">(</span><span class="mi">3</span> <span class="k">rows</span><span class="p">)</span>

<span class="c1">-- Setting a song position to a value more than the number of</span>
<span class="c1">-- songs will place the song last.</span>
<span class="k">select</span> <span class="n">set_song_position</span><span class="p">(</span><span class="s1">'Playlist A'</span><span class="p">,</span> <span class="s1">'Song A'</span><span class="p">,</span> <span class="mi">99</span><span class="p">);</span>
<span class="o">&gt;</span>  <span class="n">set_song_position</span>
<span class="o">&gt;</span> <span class="c1">-------------------</span>
<span class="o">&gt;</span> 
<span class="o">&gt;</span> <span class="p">(</span><span class="mi">1</span> <span class="k">row</span><span class="p">)</span>

<span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">playlists_numbered</span>
    <span class="k">where</span> <span class="n">playlist_name</span> <span class="o">=</span> <span class="s1">'Playlist A'</span><span class="p">;</span>
<span class="o">&gt;</span>  <span class="k">position</span> <span class="o">|</span> <span class="n">playlist_name</span> <span class="o">|</span> <span class="n">song_name</span>
<span class="o">&gt;</span> <span class="c1">----------+---------------+-----------</span>
<span class="o">&gt;</span>         <span class="mi">1</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">B</span>
<span class="o">&gt;</span>         <span class="mi">2</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="k">C</span>
<span class="o">&gt;</span>         <span class="mi">3</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">A</span>
<span class="o">&gt;</span> <span class="p">(</span><span class="mi">3</span> <span class="k">rows</span><span class="p">)</span>

<span class="c1">-- Inserting a new song at an in-between position places the new</span>
<span class="c1">-- song before the song at position 99.</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">playlists</span> <span class="k">values</span><span class="p">(</span><span class="s1">'Playlist A'</span><span class="p">,</span> <span class="s1">'Song D'</span><span class="p">,</span> <span class="mi">4</span><span class="p">);</span>
<span class="o">&gt;</span> <span class="k">INSERT</span> <span class="mi">0</span> <span class="mi">1</span>

<span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">playlists_numbered</span>
    <span class="k">where</span> <span class="n">playlist_name</span> <span class="o">=</span> <span class="s1">'Playlist A'</span><span class="p">;</span>
<span class="o">&gt;</span>  <span class="k">position</span> <span class="o">|</span> <span class="n">playlist_name</span> <span class="o">|</span> <span class="n">song_name</span>
<span class="o">&gt;</span> <span class="c1">----------+---------------+-----------</span>
<span class="o">&gt;</span>         <span class="mi">1</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">B</span>
<span class="o">&gt;</span>         <span class="mi">2</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="k">C</span>
<span class="o">&gt;</span>         <span class="mi">3</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">D</span>
<span class="o">&gt;</span>         <span class="mi">4</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">A</span>
<span class="o">&gt;</span> <span class="p">(</span><span class="mi">4</span> <span class="k">rows</span><span class="p">)</span>

<span class="c1">-- This fails. We want Song B to be at position 3, but the</span>
<span class="c1">-- result shows that Song D shows up at position 3.</span>
<span class="k">select</span> <span class="n">set_song_position</span><span class="p">(</span><span class="s1">'Playlist A'</span><span class="p">,</span> <span class="s1">'Song B'</span><span class="p">,</span> <span class="mi">3</span><span class="p">);</span>
<span class="o">&gt;</span>  <span class="n">set_song_position</span>
<span class="o">&gt;</span> <span class="c1">-------------------</span>
<span class="o">&gt;</span> 
<span class="o">&gt;</span> <span class="p">(</span><span class="mi">1</span> <span class="k">row</span><span class="p">)</span>

<span class="k">select</span> <span class="o">*</span> <span class="k">from</span> <span class="n">playlists_numbered</span>
    <span class="k">where</span> <span class="n">playlist_name</span> <span class="o">=</span> <span class="s1">'Playlist A'</span><span class="p">;</span>
<span class="o">&gt;</span>  <span class="k">position</span> <span class="o">|</span> <span class="n">playlist_name</span> <span class="o">|</span> <span class="n">song_name</span>
<span class="o">&gt;</span> <span class="c1">----------+---------------+-----------</span>
<span class="o">&gt;</span>         <span class="mi">1</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="k">C</span>
<span class="o">&gt;</span>         <span class="mi">2</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">B</span>
<span class="o">&gt;</span>         <span class="mi">3</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">D</span>
<span class="o">&gt;</span>         <span class="mi">4</span> <span class="o">|</span> <span class="n">Playlist</span> <span class="n">A</span>    <span class="o">|</span> <span class="n">Song</span> <span class="n">A</span>
<span class="o">&gt;</span> <span class="p">(</span><span class="mi">4</span> <span class="k">rows</span><span class="p">)</span>

<span class="k">create</span> <span class="k">or</span> <span class="k">replace</span> <span class="k">function</span> <span class="n">set_song_position_v2</span><span class="p">(</span><span class="n">p_playlist</span> <span class="nb">text</span><span class="p">,</span> <span class="n">p_song</span> <span class="nb">text</span><span class="p">,</span> <span class="n">p_position</span> <span class="nb">int</span><span class="p">)</span>
<span class="k">returns</span> <span class="n">void</span> <span class="k">as</span>
<span class="err">$$</span>
<span class="k">declare</span>
    <span class="n">pos</span> <span class="nb">int</span> <span class="p">:</span><span class="o">=</span> <span class="mi">0</span><span class="p">;</span>
<span class="k">begin</span>
    <span class="n">pos</span> <span class="p">:</span><span class="o">=</span> <span class="p">(</span>
        <span class="k">select</span>  <span class="n">coalesce</span><span class="p">(</span><span class="k">min</span><span class="p">(</span><span class="n">song_position</span><span class="p">),</span> <span class="n">p_position</span><span class="p">)</span> <span class="k">as</span> <span class="n">pos</span> <span class="k">from</span> <span class="p">(</span>
            <span class="k">select</span>  <span class="n">row_number</span><span class="p">()</span> <span class="n">over</span> <span class="p">(</span><span class="k">partition</span> <span class="k">by</span> <span class="n">playlist_name</span> <span class="k">order</span> <span class="k">by</span> <span class="n">song_position</span> <span class="k">asc</span><span class="p">,</span> <span class="n">position_ts</span> <span class="k">desc</span><span class="p">)</span> <span class="k">as</span> <span class="k">position</span><span class="p">,</span>
                    <span class="n">playlist_name</span><span class="p">,</span>
                    <span class="n">song_name</span><span class="p">,</span>
                    <span class="n">song_position</span>
            <span class="k">from</span>   <span class="p">(</span><span class="k">select</span>  <span class="n">playlist_name</span><span class="p">,</span>
                            <span class="n">song_name</span><span class="p">,</span>
                            <span class="n">song_position</span><span class="p">,</span>
                            <span class="n">position_ts</span>
                    <span class="k">from</span>    <span class="n">playlists</span>
                    <span class="k">where</span>   <span class="n">playlist_name</span> <span class="o">=</span> <span class="n">p_playlist</span>
                    <span class="k">and</span>     <span class="n">song_name</span> <span class="o">&lt;&gt;</span> <span class="n">p_song</span> <span class="c1">-- exclude the song we're manipulating</span>
                   <span class="p">))</span>
        <span class="k">where</span> <span class="k">position</span> <span class="o">&gt;=</span> <span class="n">p_position</span>
    <span class="p">);</span>

    <span class="n">raise</span> <span class="n">notice</span> <span class="s1">'Position is %'</span><span class="p">,</span> <span class="n">pos</span><span class="p">;</span>

    <span class="k">update</span>  <span class="n">playlists</span>
    <span class="k">set</span>     <span class="n">song_position</span> <span class="o">=</span> <span class="n">pos</span>
    <span class="k">where</span>   <span class="n">playlist_name</span> <span class="o">=</span> <span class="n">p_playlist</span>
    <span class="k">and</span>     <span class="n">song_name</span> <span class="o">=</span> <span class="n">p_song</span><span class="p">;</span>
<span class="k">end</span><span class="p">;</span>
<span class="err">$$</span> <span class="k">language</span> <span class="n">plpgsql</span><span class="p">;</span>
<span class="o">&gt;</span> <span class="k">CREATE</span> <span class="k">FUNCTION</span>

<span class="c1">-- Clean up</span>
<span class="k">drop</span> <span class="k">table</span> <span class="n">playlists</span> <span class="k">cascade</span><span class="p">;</span> <span class="c1">-- cascade drops views of the table</span>
<span class="n">psql</span><span class="p">:</span><span class="n">playlists</span><span class="p">.</span><span class="k">sql</span><span class="p">:</span><span class="mi">133</span><span class="p">:</span> <span class="n">NOTICE</span><span class="p">:</span>  <span class="k">drop</span> <span class="n">cascades</span> <span class="k">to</span> <span class="mi">2</span> <span class="n">other</span> <span class="n">objects</span>
<span class="n">DETAIL</span><span class="p">:</span>  <span class="k">drop</span> <span class="n">cascades</span> <span class="k">to</span> <span class="k">view</span> <span class="n">playlists_numbered</span>
<span class="k">drop</span> <span class="n">cascades</span> <span class="k">to</span> <span class="k">view</span> <span class="n">playlists_ordered</span>
<span class="o">&gt;</span> <span class="k">DROP</span> <span class="k">TABLE</span>

<span class="k">drop</span> <span class="k">function</span> <span class="n">update_playlist_position_ts</span><span class="p">();</span>
<span class="o">&gt;</span> <span class="k">DROP</span> <span class="k">FUNCTION</span>

<span class="k">drop</span> <span class="k">function</span> <span class="n">set_song_position</span><span class="p">(</span><span class="nb">text</span><span class="p">,</span> <span class="nb">text</span><span class="p">,</span> <span class="nb">int</span><span class="p">);</span>
<span class="o">&gt;</span> <span class="k">DROP</span> <span class="k">FUNCTION</span>

<span class="k">drop</span> <span class="k">function</span> <span class="n">set_song_position_v2</span><span class="p">(</span><span class="nb">text</span><span class="p">,</span> <span class="nb">text</span><span class="p">,</span> <span class="nb">int</span><span class="p">);</span>
<span class="o">&gt;</span> <span class="k">DROP</span> <span class="k">FUNCTION</span>

</code></pre></div></div>]]></content><author><name></name></author><category term="blog" /><category term="Postgres," /><category term="Data" /><category term="type," /><category term="experiment" /><summary type="html"><![CDATA[&lt;!– ChatGPT rephrased the blog as follows. The prompt was:]]></summary></entry><entry><title type="html">Force Docker CLI to connect to Podman, on macOS</title><link href="http://gurjeet.singh.im/blog/force-docker-cli-to-connect-to-podman" rel="alternate" type="text/html" title="Force Docker CLI to connect to Podman, on macOS" /><published>2023-04-17T05:48:16+00:00</published><updated>2023-04-17T05:48:16+00:00</updated><id>http://gurjeet.singh.im/blog/force-docker-cli-to-connect-to-podman</id><content type="html" xml:base="http://gurjeet.singh.im/blog/force-docker-cli-to-connect-to-podman"><![CDATA[<p>Podman is a replacement of Docker. Podman does a great job of being compatible
with Docker’s tools, utilities, etc. So if you have workflows, scripts, etc.
developed for Docker, rather than changing all of them, it’s easier to simply
change one environment variable and let Docker tools believe that they’re
communicating with Docker server, whereas, in reality, it’s Podman that does the
real work.</p>

<p>On macOS, Podman runs inside a virtual machine. And it exposes its
communication channel as a Unix Domain Socket. So you can set the <code class="language-plaintext highlighter-rouge">DOCKER_HOST</code>
environment variable to point to Podman server’s socket, and the Doccker CLI
utiliies will all work with Podman.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">DOCKER_HOST</span><span class="o">=</span><span class="s2">"unix://</span><span class="si">$(</span>podman machine inspect <span class="nt">--format</span> <span class="s1">'{{.ConnectionInfo.PodmanSocket.Path}}'</span><span class="si">)</span><span class="s2">"</span>
</code></pre></div></div>

<p>Rather than remembering that incantation, I turned it into a Bash function with
an easy-to-remember name, and some helpful diagnostics.</p>

<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function </span>docker_set_host_to_podman_socket<span class="o">()</span>
<span class="o">{</span>
    <span class="nb">local </span><span class="nv">socket_path</span><span class="o">=</span><span class="s2">"</span><span class="si">$(</span>podman machine inspect <span class="nt">--format</span> <span class="s1">'{{.ConnectionInfo.PodmanSocket.Path}}'</span><span class="si">)</span><span class="s2">"</span>
    <span class="nb">local </span><span class="nv">unix_domain_socket</span><span class="o">=</span><span class="s2">"unix://</span><span class="nv">$socket_path</span><span class="s2">"</span>
    <span class="nb">echo</span> <span class="s2">"Setting DOCKER_HOST=</span><span class="nv">$unix_domain_socket</span><span class="s2">"</span>
    <span class="nb">export </span><span class="nv">DOCKER_HOST</span><span class="o">=</span><span class="s2">"</span><span class="nv">$unix_domain_socket</span><span class="s2">"</span>
<span class="o">}</span>
</code></pre></div></div>]]></content><author><name></name></author><category term="blog" /><category term="Docker," /><category term="Podman," /><category term="macOS" /><summary type="html"><![CDATA[Podman is a replacement of Docker. Podman does a great job of being compatible with Docker’s tools, utilities, etc. So if you have workflows, scripts, etc. developed for Docker, rather than changing all of them, it’s easier to simply change one environment variable and let Docker tools believe that they’re communicating with Docker server, whereas, in reality, it’s Podman that does the real work.]]></summary></entry><entry><title type="html">Supabase Contributes a Contributor to the Postgres Community</title><link href="http://gurjeet.singh.im/blog/supabase-contributes-a-contributor-to-postgres-community" rel="alternate" type="text/html" title="Supabase Contributes a Contributor to the Postgres Community" /><published>2021-08-31T16:31:43+00:00</published><updated>2021-08-31T16:31:43+00:00</updated><id>http://gurjeet.singh.im/blog/supabase-contributes-a-contributor-to-postgres-community</id><content type="html" xml:base="http://gurjeet.singh.im/blog/supabase-contributes-a-contributor-to-postgres-community"><![CDATA[<p>Update ca. 2023: I no longer work at Supabase.</p>

<p>Yesterday I joined <a href="https://supabase.io">Supabase</a>; they provide Backend-as-a-Service, which is
developed on top of their Postgres-as-a-Service offering. They provide many
features, utilities, libraries, and services around the Postgres database that
makes it very easy to develop applications.</p>

<p>I’m very happy to say that they have very generously allowed me to focus
explicitly on contributing to the Postgres community. This is truly a dream
come true!</p>

<p>If you happen to be a Firebase customer, but miss the powers of the SQL
language, do check out <a href="https://supabase.io">Supabase</a>.</p>

<p>Here’s the announcement I posted to the Postgres Hackers mailing list, with some
more details and a statement from the founders of Supabase.</p>

<p><a href="https://www.postgresql.org/message-id/CABwTF4UZzYoHcaEGe2cESVn-e9dc3wzg%3Dmvx7Tz8qbKJ7p3UKA%40mail.gmail.com">https://www.postgresql.org/message-id/CABwTF4UZzYoHcaEGe2cESVn-e9dc3wzg%3Dmvx7Tz8qbKJ7p3UKA%40mail.gmail.com</a></p>]]></content><author><name></name></author><category term="blog" /><category term="Postgres," /><category term="Community," /><category term="Contributions," /><category term="Open" /><category term="Source" /><summary type="html"><![CDATA[Update ca. 2023: I no longer work at Supabase.]]></summary></entry><entry><title type="html">Postgres turns 25 years old today (or is it 35?)</title><link href="http://gurjeet.singh.im/blog/postgres-is-25-years-old" rel="alternate" type="text/html" title="Postgres turns 25 years old today (or is it 35?)" /><published>2021-07-09T02:41:59+00:00</published><updated>2021-07-09T02:41:59+00:00</updated><id>http://gurjeet.singh.im/blog/postgres-is-25-years-old</id><content type="html" xml:base="http://gurjeet.singh.im/blog/postgres-is-25-years-old"><![CDATA[<h1 id="when-was-postgres-born">When was Postgres born?</h1>

<p>It is very difficult to pin down the date when Postgres (a.k.a PostgreSQL)
project was born. If you look at the <a href="https://www.postgresql.org/docs/current/history.html">official history</a> on the Postgres
website, you’d see a few years mentioned, as early as the year 1986, but no
specific dates.</p>

<h1 id="unofficial-birth-timestamp-of-postgres">Unofficial Birth Timestamp of Postgres</h1>

<p>If we go by the timestamp of the <a href="https://git.postgresql.org/gitweb/?p=postgresql.git;a=commit;h=d31084e9d1118b25fd16580d9d8c2924b5740dff">first commit</a> in Postgres code
repository, the project was started at ‘Tue Jul 9 06:22:35 1996 +0000’. Going by
that date, today the Postgres project turns 25 years old.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ psql -c "select age(now(), 'Tue Jul 9 06:22:35 1996 +0000')"
                   age
------------------------------------------
 24 years 11 mons 30 days 22:39:46.830704
(1 row)
</code></pre></div></div>

<p>Oh, it’s so close…</p>

<div class="tenor-gif-embed" data-postid="14490824" data-share-method="host" data-width="50%" data-aspect-ratio="0.8955823293172691"><a href="https://tenor.com/view/excited-party-dance-puppy-gif-14490824">Excited Party GIF</a> from <a href="https://tenor.com/search/excited-gifs">Excited GIFs</a></div>

<p><br />
Don’t be like this</p>

<div class="tenor-gif-embed" data-postid="13182748" data-share-method="host" data-width="50%" data-aspect-ratio="1.7913669064748199"><a href="https://tenor.com/view/duck-dynasty-reality-tv-getting-old-birthday-not-excited-gif-13182748">Duck Dynasty Reality TV GIF</a> from <a href="https://tenor.com/search/duckdynasty-gifs">Duckdynasty GIFs</a></div>

<script type="text/javascript" async="" src="https://tenor.com/embed.js"></script>

<h1 id="future">Future</h1>

<p>If you haven’t been following the progress of the Postgres project, I implore
you to do so now (see below). The future of the Postgres project is very bright.
Its feature set has been growing steadily, while retaining its legendary
commitment to stability and correctness.</p>

<h1 id="follow-postgres">Follow Postgres</h1>

<p>We have these awesome <a href="https://www.postgresql.org/community/contributors/">contributors</a>, and many others who are not listed
there, to thank for the current state of their projects, and their tireless
work.</p>

<p>These contributors, and many others, blog regularly about what’s happening in
the Postgres world, and those blog posts are aggregated at <a href="https://planet.postgresql.org/">Planet
Postgres</a>. Feel free to subscribe to any of the many RSS feeds there
(even by an author you may be interested in).</p>

<p>Here are many other channels to engage with Postgres community:</p>

<ul>
  <li>Twitter: <a href="https://twitter.com/PostgreSQL">@PostgreSQL</a>, and <a href="https://twitter.com/planetpostgres">@PlanetPostgres</a></li>
  <li><a href="https://www.postgresql.org/list/">Mailing Lists</a></li>
  <li><a href="https://www.postgresql.org/community/irc/">IRC</a></li>
  <li><a href="https://www.linkedin.com/company/postgresql-global-development-group/mycompany/">LinkedIn</a></li>
</ul>]]></content><author><name></name></author><category term="blog" /><category term="postgres" /><category term="foss" /><summary type="html"><![CDATA[When was Postgres born?]]></summary></entry><entry><title type="html">Waking Up, by Sam Harris, Generous Free Offer</title><link href="http://gurjeet.singh.im/blog/waking-up-by-sam-harris-generous-free-offer" rel="alternate" type="text/html" title="Waking Up, by Sam Harris, Generous Free Offer" /><published>2021-07-05T23:39:09+00:00</published><updated>2021-07-05T23:39:09+00:00</updated><id>http://gurjeet.singh.im/blog/waking-up-by-sam-harris-generous-free-offer</id><content type="html" xml:base="http://gurjeet.singh.im/blog/waking-up-by-sam-harris-generous-free-offer"><![CDATA[<p>I really like the methods and philosophy of the <a href="https://www.wakingup.com/">Waking Up</a> app by Sam
Harris. I think it’s a great resource for those who want to try meditation,
without any connotations of religion attached to it. This is my attempt at
spreading the word.</p>

<p>The app has a unique pricing model that I haven’t seen anyone else offer. They
offer the app for free to anyone who asks. Here’s the poicy in Sam’s own words
(captured from their <a href="https://app.wakingup.com/free-account">video</a>). I couldn’t find this written anywhere on
their website, so I typed it up and published it here.</p>

<blockquote>

  <p>So I want to take a moment to explain our business philosophy.</p>

  <p>Our intent is for everyone who wants access to all the content on the app to
have it.</p>

  <p>So while we’re running a business, we believe that money should never be the
reason why someone can’t gain access to Waking Up. We also don’t want you to
feel you’re risking anything by subscribing.</p>

  <p>So here’s our pricing policy: if you can’t afford a subscription, just request a
free account.</p>

  <p>You can do this directly at wakingup.com. There’s a “request a free account”
link or, you can contact support within the app, or you can send an email to
support@wakingup.com. And if you do one of these things, will provide you with
a free year on the app.</p>

  <p>And if you’re uncertain about whether this policy applies to you, here’s how I
think about it. If subscribing to Waking Up will prevent you from spending money
on something else, in other words, if you have to do the math to see how the
cost of a membership can fit into your life, please don’t do that. This free
policy is for you.</p>

  <p>I don’t want anyone’s subscription to Waking Up to be a source of financial
stress. And I consider myself immensely lucky to be running a digital business
where we can have a policy like this. And this policy is not for the COVID-19
pandemic. It’s been our policy since the day we launched. So the only judge of
whether you can afford a subscription is you.</p>

  <p>We don’t ask any questions, and we grant 100 per cent of these requests. And if
your luck hasn’t changed at the end of your first year, just ask for another one.</p>

  <p>For everyone else a subscription to Waking Up is truly risk-free. If you don’t
find the app valuable, just contact support, and will give you a full refund,
again, no questions asked.</p>

  <p>So whatever your financial situation, there’s no reason not to fully explore
Waking Up to see how it impacts your life.</p>

  <p>So I encourage you to just start with the Introductory Course, and feel free to
go at your own pace. And needless to say, you can always go back and repeat
sessions as many times as you want. And please realize there’s a world of
difference between practicing, even only occasionally, and not practicing at
all.</p>

  <p>Once again, welcome to Waking Up, and I want to thank you in advance for the
effort you put in here. I think you’ll find that it’s worth it.</p>
</blockquote>]]></content><author><name></name></author><category term="blog" /><category term="Pricing" /><category term="Model," /><category term="Generosity," /><category term="Business," /><category term="Startup," /><category term="App" /><summary type="html"><![CDATA[I really like the methods and philosophy of the Waking Up app by Sam Harris. I think it’s a great resource for those who want to try meditation, without any connotations of religion attached to it. This is my attempt at spreading the word.]]></summary></entry><entry><title type="html">No Golang For You</title><link href="http://gurjeet.singh.im/blog/no-golang-for-you" rel="alternate" type="text/html" title="No Golang For You" /><published>2021-01-02T12:08:00+00:00</published><updated>2021-01-02T12:08:00+00:00</updated><id>http://gurjeet.singh.im/blog/no-golang-for-you</id><content type="html" xml:base="http://gurjeet.singh.im/blog/no-golang-for-you"><![CDATA[<p>The blog post <a href="https://fasterthanli.me/articles/i-want-off-mr-golangs-wild-ride">“I want off Mr. Golang’s Wild Ride”</a> by Amos incited
me to write a comment on its <a href="https://news.ycombinator.com/item?id=25616593">HN</a> submission page. But as the comment ran 5
paragraphs long, I have turned it into this blog post.</p>

<p>It is a well written and in-depth look at the rot inside the Golang ecosystem.
Make sure to read to the end and notice that the rot started at the core
contributors level.</p>

<p>Golang’s tagline, from their <a href="https://github.com/golang/go">repository</a>, is “Go is an open source
programming language that makes it easy to build simple, reliable, and efficient
software.”</p>

<p>Reading this post by Amos should make it clear to you that Golang has succeeded
ONLY in the “simple” part of the claim; unless written with extreme care,
programs written in Golang are neither reliable, nor efficient.</p>

<p>Amos presents some examples of why I have disliked Golang since the beginning.
After having used Golang for a few years seriously, I disliked it enough to
start a fork of the language named <a href="https://github.com/gurjeet/gofy/tree/gofy">GoFY</a>. I named the language as such
because, since the beginning when I saw some of initial presentations by Rob
Pike specifically, and some others’ presentations and blog posts generally, they
had an air of ego when dismissing others’ opinions and concerns, and appeared to
say “these are our decisions, that is how it is, and if you don’t like it, Go
F*** Yourself”. So the project name was a GoFY to them back, from me.</p>

<p>I’m sure it looks a great language from afar, and for newcomers. But as a
seasoned developer who has seen a few other languages in thier lifetime, you
will quickly begin to see the problems with the language and the ecosystem
around it.  The astute among you may notice that in my <a href="https://gurjeet.singh.im/blog/persistence-perseverance">post yesterday</a> I
did not claim to be an expert in Golang; it’s hard to be an expert in something
that’s fragile, flaky and full of special cases. For the same reason I
never took up using MySQL; I have heard enough stories of its flakiness and
special cases that it never looked like a solid technology to me. I openly and
whole-heartedly recommend learning and using <a href="https://www.postgresql.org">Postgres</a> to anyone who would
listen.</p>

<p>Fortunately I did not invest any more time in it other than to document a few
times in section <a href="https://github.com/gurjeet/gofy/tree/gofy#gofy-desired-differences">“GoFY Desired Differences”</a> as to what I would
like to see different in the GoFY language, which in turn would make it better
than Golang, at least for long-time systems developers like myself. I am glad I
didn’t burn any oil on it because creating a new language, even a fork, is
neither easy nor quick.</p>]]></content><author><name></name></author><category term="blog" /><category term="Golang" /><summary type="html"><![CDATA[The blog post “I want off Mr. Golang’s Wild Ride” by Amos incited me to write a comment on its HN submission page. But as the comment ran 5 paragraphs long, I have turned it into this blog post.]]></summary></entry><entry><title type="html">Persistence, Perseverance</title><link href="http://gurjeet.singh.im/blog/persistence-perseverance" rel="alternate" type="text/html" title="Persistence, Perseverance" /><published>2021-01-01T15:22:00+00:00</published><updated>2021-01-01T15:22:00+00:00</updated><id>http://gurjeet.singh.im/blog/persistence-perseverance</id><content type="html" xml:base="http://gurjeet.singh.im/blog/persistence-perseverance"><![CDATA[<p>TL;DR version:</p>
<ul>
  <li>Pursue the technology<sup>+</sup> that interests you</li>
  <li>Get your hands dirty trying to solve your problem (a.k.a scratch your own itch)</li>
  <li>Don’t give up (at least not until you’ve exhausted all the possibilities)
    <ul>
      <li>Persist, Persevere</li>
    </ul>
  </li>
  <li>Reap the benefits of hard-earned knowledge for the rest of your life</li>
</ul>

<p>Somebody recently wrote on Hacker News that the knowledge gained through hard
work is the one that sticks with you. So I wanted to present some data points to
back my agreement with that sentiment.</p>

<p>I am a known person in Postgres community<sup>*</sup> (<a href="http://bit.ly/1q208OG">commits</a>; <a href="http://bit.ly/1oeQaUu">emails</a>), and it
did not happen by accident. Some 15 years ago, after changing employers (for
personal reasons) from
Oracle to CommVault, I was attracted towards Postgres database project, so I
started exploring its code and participating in their conversations outside of
work. Within a
few months I was pursued by EnterpriseDB to work on their Postgres-based product full-time.</p>

<p>Bulk of my contributions to Postgres happened during my time at EnterpriseDB,
and most of my contributions were simply something I wanted to work on.
Delivering on any of these required turning off distractions and diving deep and
removing one blocker after another until there was something polished enough for
me to feel satisfied with, and presentable to the Postgres community. The same
is true of any significant contributions I’ve made to my employers’ projects.
The formula is simple: turn off distractions, and slog it out. Now, there may be
many reasons to <em>not</em> want to achieve a goal, but if you want to achive it,
that’s the only formula.</p>

<p>More recently, I have been enamored by the core principle of the Nix package
manager. I first got seriously impressed by it in September of 2018, and tried
to learn and use it. But like most others, every attempt of mine to learn it at
any depth proved futile, primarily because the learning curve is <em>extremely</em>
steep; the functional programming language, Nix, being the first hurdle. Despite
the benefits it brings to the table, it is a serious undertaking for any
individual, and hence even more difficult to adopt in a company, at your job and
start leveraging its advantages; this is especially true of a technology like
Nix that is novel, but is simply replacing something that generally works and
people/companies have learned to live with.</p>

<p>When I got a new personal laptop a few months ago, I decided to use Nix instead
of Homebrew to install the packages I needed. The journey has been smooth so
far, and am able to get on with my personal work just fine.</p>

<p>Over the winter break I was working on a side project and I hit a snag in my Vim
usage. I had recently uninstalled the <code class="language-plaintext highlighter-rouge">vim</code> package and replaced it with
<code class="language-plaintext highlighter-rouge">vim_configurable</code>, because the latter provided Python3 support which I needed
for some of my Vim plugins. So, like any other developer (and like <a href="https://www.youtube.com/watch?v=AbSehcT19u0">Hal</a>), I
got sidetracked to fix my Vim. It took me 3 days but I finally made my
first non-trivial <a href="https://github.com/NixOS/nixpkgs/pull/108109/files">contribution</a> to Nixpkgs; yes, as the world was celebrating
new year, I was creating the pull/merge-request in the middle of the night<sup>**</sup>.</p>

<p>It could’ve taken me less than a day if I had just posted what I wanted to get
fixed on one of the various avenues the NixOS/Nixpkgs people hang out in. They
would have given me a neat trick to solve my problem and let me get on with
life. And frankly I have done that so many times in the past where, to be able
to get on with whatever I am doing, I would ask around on the internet, wait for a decent answer
that works, and move on.</p>

<p>But I took it upon myself to get exactly what I need. As usual, I started with
trial and error based on what’s already in Nixpkgs. When that didn’t work I
searched online for a problem similar enough to mine. When that did not help me
fix my problem, I found the good Nix/NixOS teaching videos in my YouTube history
and spent ~6 hours watching those and noticing things that hadn’t made sense the
first time. Followed along doing the small exercises on their slides. All this
while taking care of other life stuff happening on the side; but my side-project
had been suspended while I tired all this. After 3 days of learning, 40+ tabs
still open in my browser, I finally felt confident enough to try writing the Nix
code I needed. I still hit a few bumps, but I was able to reason about the error
messages, debug and resolve the errors.</p>

<p>The things that I learned about Nix and Nixpkgs over these 3 days, I could <em>not</em>
have learnt, and definitely could not have retained that knowledge, had I simply
asked someone else to solve my problem. Now, given my knowledge, I feel
confident that I will be able to apply this in other projects and benefit from
it; I may still be slower than the Nix gurus, but I can get it done. As they
say, I now know enough to be dangerous :) For example, I now intuitively know
why <code class="language-plaintext highlighter-rouge">nix-env -iA xxx</code> is so much faster than <code class="language-plaintext highlighter-rouge">nix-env -i xxx</code>; answer: because
the <code class="language-plaintext highlighter-rouge">-A</code> switch lets Nix pick just the variable you’re interested in, and lazily
evaluate just that expression, rather than evaluate all the expressions in
Nixpkgs and search for a <em>package</em> named xxx.</p>

<p>Over my 20 years of experience I have seen this to be the case, it’s only now
that I realized the pattern where I have reaped huge rewards from small but
difficult investments. Without even realizing it, I have invested my efforts
like this time and again, and gained significant expertise in the C language, Git, Docker, Kubernetes, and Postgres. There have
been some things that I was interested in (RISC-V comes to mind) but I never
invested enough time beyond cursory participation, and as a result I don’t have
enough knowledge to call myslef an expert in them.</p>

<p>Not to say that all time spent in such deep/in-the-zone work always bears a
fruit, and to give an idea of how time spent like this, many a times goes to
waste… Over this same 3 day period I encountered a behaviour in Postgres’
psql utility that seemed like a bug, or at least a surprising behaviour. So I
carefully developed a test case that would demonstrate the problem, studied the
psql code and found the place where code would need to be fixed. I stopped short
of developing a patch before starting to write email to pgsql-hackers mailing
list. While I was writing the email, on a whim I decided to read the
documentation. A cursory read of the docs wouldn’t have caught it, but because I
was reading the docs to look for mention of the exact behaviour I was seeing, I
found that it was an expected behaviour.  Even though the psql behaviour
violated <a href="https://en.wikipedia.org/wiki/Principle_of_least_astonishment">POLA</a>, I could not report the bug because it was known and
expected behaviour. All the effort spent in doing that went down the drain.</p>

<p>In essence,</p>
<ul>
  <li>Pursue the technology<sup>+</sup> that interests you</li>
  <li>Get your hands dirty trying to solve your problem (a.k.a scratch your own itch)</li>
  <li>Don’t give up (at least not until you’ve exhausted all the possibilities)
    <ul>
      <li>Persist, Persevere</li>
    </ul>
  </li>
  <li>Reap the benefits of hard-earned knowledge for the rest of your life</li>
</ul>

<p><em>footnote</em> +: If your field of work has <em>techniques</em>, that means it’s a
technology to be learnt and mastered; so in my veiw, fields like art and
politics are also technologies. The technology in these fields may not move fast, but it is still a
technology to learn and improve on.</p>

<p><em>footnote</em> *: even though I haven’t <em>contributed</em> to the community in any significant
capacity in the last few years.</p>

<p><em>footnote</em> **: I was home alone, since my family is on a trip to India. To be
fair, that was one of the big reasons it took 3 days and not 3 weeks, and very
likely why I did not give up on the goal.</p>]]></content><author><name></name></author><category term="blog" /><category term="Persistence," /><category term="Perseverance," /><category term="Hard" /><category term="Work," /><category term="Knowledge," /><category term="Compound" /><category term="Interest" /><summary type="html"><![CDATA[TL;DR version: Pursue the technology+ that interests you Get your hands dirty trying to solve your problem (a.k.a scratch your own itch) Don’t give up (at least not until you’ve exhausted all the possibilities) Persist, Persevere Reap the benefits of hard-earned knowledge for the rest of your life]]></summary></entry></feed>