From d274d8d7efe38cc9fe21494cc4ce9de346dc9375 Mon Sep 17 00:00:00 2001 From: Ben Newman Date: Thu, 12 Mar 2020 17:18:20 -0400 Subject: [PATCH] Work around bizarre SQLite dynamic type conversion behavior. SQLite has a worse-is-better philosophy about automatically converting between different data types, such as strings and floating point numbers: https://www.sqlite.org/quirks.html#flexible_typing This means querying for the string "1.10" in a given column can return rows where the column is actually the string "1.1", since SQLite imagines you might be talking about the number 1.1, rather than the string you actually requested. This "feature" became a problem for Meteor after we published Meteor 1.10, which caused SQLite to return multiple rows for the getReleaseVersion query, including both Meteor 1.10 and Meteor 1.1 (which is ancient, from March 2015). While this behavior seems completely indefensible, the SQLite documentation clearly does not consider it a bug, which forces us to work around the consequences by double-checking the queried results with the filterExactRows helper function. --- tools/packaging/catalog/catalog-remote.js | 40 ++++++++++++++++------- 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/tools/packaging/catalog/catalog-remote.js b/tools/packaging/catalog/catalog-remote.js index c6a242f9c3..41c1bad686 100644 --- a/tools/packaging/catalog/catalog-remote.js +++ b/tools/packaging/catalog/catalog-remote.js @@ -536,14 +536,11 @@ _.extend(RemoteCatalog.prototype, { self.db = null; }, - getVersion: function (name, version) { + getVersion: function (packageName, version) { var result = this._contentQuery( "SELECT content FROM versions WHERE packageName=? AND version=?", - [name, version]); - if(!result || result.length === 0) { - return null; - } - return result[0]; + [packageName, version]); + return filterExactRows(result, { packageName, version }); }, // As getVersion, but returns info on the latest version of the @@ -658,9 +655,7 @@ _.extend(RemoteCatalog.prototype, { var self = this; var result = self._contentQuery( "SELECT content FROM releaseTracks WHERE name=?", name); - if (!result || result.length === 0) - return null; - return result[0]; + return filterExactRows(result, { name }); }, getReleaseVersion: function (track, version) { @@ -668,9 +663,7 @@ _.extend(RemoteCatalog.prototype, { var result = self._contentQuery( "SELECT content FROM releaseVersions WHERE track=? AND version=?", [track, version]); - if (!result || result.length === 0) - return null; - return result[0]; + return filterExactRows(result, { track, version }); }, // Used by make-bootstrap-tarballs. Only should be used on catalogs that are @@ -996,6 +989,29 @@ _.extend(RemoteCatalog.prototype, { } }); +// SQLite has a bizarre philosophy about automaticaly converting between +// different data types, such as strings and floating point numbers: +// https://www.sqlite.org/quirks.html#flexible_typing +// +// This means querying for the string "1.10" in a given column can return +// rows where the column is actually the string "1.1", since SQLite thinks +// you might be talking about the number 1.1 rather than the string you +// actually requested. +// +// This "feature" first became a problem for Meteor after we published +// Meteor 1.10, which caused SQLite to return multiple rows for the +// getReleaseVersion query, including both 1.10 and 1.1.1 (ancient). +// +// While this policy seems completely indefensible, the SQLite project +// does not consider it a bug, which forces us to work around it by +// double-checking the queried results with this helper function: +function filterExactRows(rows, requirements) { + const keys = Object.keys(requirements); + return rows && rows.filter(row => { + return keys.every(key => row[key] === requirements[key]); + })[0] || null; +} + exports.RemoteCatalog = RemoteCatalog; // We put this constant here because we don't have any better place that would otherwise cause a cycle