add column 'last_lu_rat', show last RAN type

Change-Id: I5d73b1d928b61175d3198326706b7f49ba50e16f
diff --git a/sql/hlr.sql b/sql/hlr.sql
index 3c65cd5..6ffcba1 100644
--- a/sql/hlr.sql
+++ b/sql/hlr.sql
@@ -40,7 +40,12 @@
 
 	-- Timestamp of last location update seen from subscriber
 	-- The value is a string which encodes a UTC timestamp in granularity of seconds.
-	last_lu_seen TIMESTAMP default NULL
+	last_lu_seen TIMESTAMP default NULL,
+
+	-- Last Radio Access Type list as sent during Location Updating Request.
+	-- This is usually just one RAT name, but can be a comma separated list of strings
+	-- of all the RAT types sent during Location Updating Request.
+	last_lu_rat	TEXT default NULL
 );
 
 CREATE TABLE subscriber_apn (
@@ -83,4 +88,4 @@
 
 -- Set HLR database schema version number
 -- Note: This constant is currently duplicated in src/db.c and must be kept in sync!
-PRAGMA user_version = 2;
+PRAGMA user_version = 3;
diff --git a/src/db.c b/src/db.c
index 0a51178..892943a 100644
--- a/src/db.c
+++ b/src/db.c
@@ -28,7 +28,7 @@
 #include "db_bootstrap.h"
 
 /* This constant is currently duplicated in sql/hlr.sql and must be kept in sync! */
-#define CURRENT_SCHEMA_VERSION	2
+#define CURRENT_SCHEMA_VERSION	3
 
 #define SEL_COLUMNS \
 	"id," \
@@ -44,7 +44,8 @@
 	"lmsi," \
 	"ms_purged_cs," \
 	"ms_purged_ps," \
-	"last_lu_seen"
+	"last_lu_seen," \
+	"last_lu_rat"
 
 static const char *stmt_sql[] = {
 	[DB_STMT_SEL_BY_IMSI] = "SELECT " SEL_COLUMNS " FROM subscriber WHERE imsi = ?",
@@ -75,7 +76,8 @@
 		"INSERT INTO auc_3g (subscriber_id, algo_id_3g, k, op, opc, ind_bitlen)"
 		" VALUES($subscriber_id, $algo_id_3g, $k, $op, $opc, $ind_bitlen)",
 	[DB_STMT_AUC_3G_DELETE] = "DELETE FROM auc_3g WHERE subscriber_id = $subscriber_id",
-	[DB_STMT_SET_LAST_LU_SEEN] = "UPDATE subscriber SET last_lu_seen = datetime($val, 'unixepoch') WHERE id = $subscriber_id",
+	[DB_STMT_SET_LAST_LU_SEEN] = "UPDATE subscriber SET last_lu_seen = datetime($val, 'unixepoch'), last_lu_rat = $rat"
+				     " WHERE id = $subscriber_id",
 	[DB_STMT_UPD_RAT_FLAG] = "INSERT OR REPLACE INTO subscriber_rat (subscriber_id, rat, allowed)"
 		" VALUES ($subscriber_id, $rat, $allowed)",
 	[DB_STMT_RAT_BY_ID] =
@@ -341,6 +343,40 @@
 	return SQLITE_DONE;
 }
 
+static int db_upgrade_v3(struct db_context *dbc)
+{
+	sqlite3_stmt *stmt;
+	int rc;
+	const char *update_stmt_sql = "ALTER TABLE subscriber ADD COLUMN last_lu_rat TEXT default NULL";
+	const char *set_schema_version_sql = "PRAGMA user_version = 3";
+
+	rc = sqlite3_prepare_v2(dbc->db, update_stmt_sql, -1, &stmt, NULL);
+	if (rc != SQLITE_OK) {
+		LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", update_stmt_sql);
+		return rc;
+	}
+	rc = sqlite3_step(stmt);
+	db_remove_reset(stmt);
+	sqlite3_finalize(stmt);
+	if (rc != SQLITE_DONE) {
+		LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version %d\n", 1);
+		return rc;
+	}
+
+	rc = sqlite3_prepare_v2(dbc->db, set_schema_version_sql, -1, &stmt, NULL);
+	if (rc != SQLITE_OK) {
+		LOGP(DDB, LOGL_ERROR, "Unable to prepare SQL statement '%s'\n", set_schema_version_sql);
+		return rc;
+	}
+	rc = sqlite3_step(stmt);
+	if (rc != SQLITE_DONE)
+		LOGP(DDB, LOGL_ERROR, "Unable to update HLR database schema to version 3\n");
+
+	db_remove_reset(stmt);
+	sqlite3_finalize(stmt);
+	return rc;
+}
+
 static int db_get_user_version(struct db_context *dbc)
 {
 	const char *user_version_sql = "PRAGMA user_version";
@@ -462,6 +498,15 @@
 			}
 			version = 2;
 			/* fall through */
+		case 2:
+			rc = db_upgrade_v3(dbc);
+			if (rc != SQLITE_DONE) {
+				LOGP(DDB, LOGL_ERROR, "Failed to upgrade HLR DB schema to version 3: (rc=%d) %s\n",
+				     rc, sqlite3_errmsg(dbc->db));
+				goto out_free;
+			}
+			version = 2;
+			/* fall through */
 		/* case N: ... */
 		default:
 			break;
diff --git a/src/db.h b/src/db.h
index 6fd33ee..bb3a031 100644
--- a/src/db.h
+++ b/src/db.h
@@ -89,6 +89,7 @@
 	bool		ms_purged_cs;
 	bool		ms_purged_ps;
 	time_t		last_lu_seen;
+	char		last_lu_rat[128];
 	bool		rat_types[OSMO_RAT_COUNT];
 };
 
@@ -140,7 +141,8 @@
 			struct hlr_subscriber *subscr);
 int db_subscr_nam(struct db_context *dbc, const char *imsi, bool nam_val, bool is_ps);
 int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
-		 const char *vlr_or_sgsn_number, bool is_ps);
+		 const char *vlr_or_sgsn_number, bool is_ps,
+		 const enum osmo_rat_type rat_types[], size_t rat_types_len);
 
 int db_subscr_purge(struct db_context *dbc, const char *by_imsi,
 		    bool purge_val, bool is_ps);
diff --git a/src/db_hlr.c b/src/db_hlr.c
index f546e46..d1bb7b8 100644
--- a/src/db_hlr.c
+++ b/src/db_hlr.c
@@ -440,6 +440,7 @@
 			}
 		}
 	}
+	copy_sqlite3_text_to_buf(subscr->last_lu_rat, stmt, 14);
 
 out:
 	db_remove_reset(stmt);
@@ -598,11 +599,14 @@
  *         -EIO on database errors.
  */
 int db_subscr_lu(struct db_context *dbc, int64_t subscr_id,
-		 const char *vlr_or_sgsn_number, bool is_ps)
+		 const char *vlr_or_sgsn_number, bool is_ps,
+		 const enum osmo_rat_type rat_types[], size_t rat_types_len)
 {
 	sqlite3_stmt *stmt;
 	int rc, ret = 0;
 	struct timespec localtime;
+	char rat_types_str[128] = "";
+	int i;
 
 	stmt = dbc->stmt[is_ps ? DB_STMT_UPD_SGSN_BY_ID
 			       : DB_STMT_UPD_VLR_BY_ID];
@@ -656,6 +660,21 @@
 		goto out;
 	}
 
+	for (i = 0; i < rat_types_len; i++) {
+		char *pos = rat_types_str + strnlen(rat_types_str, sizeof(rat_types_str));
+		int len = pos - rat_types_str;
+		rc = snprintf(pos, len, "%s%s", pos == rat_types_str ? "" : ",", osmo_rat_type_name(rat_types[i]));
+		if (rc > len) {
+			osmo_strlcpy(rat_types_str + sizeof(rat_types_str) - 4, "...", 4);
+			break;
+		}
+	}
+
+	if (!db_bind_text(stmt, "$rat", rat_types_str)) {
+		ret = -EIO;
+		goto out;
+	}
+
 	rc = sqlite3_step(stmt);
 	if (rc != SQLITE_DONE) {
 		LOGP(DAUC, LOGL_ERROR,
diff --git a/src/hlr.c b/src/hlr.c
index 098b934..1f5e63d 100644
--- a/src/hlr.c
+++ b/src/hlr.c
@@ -356,7 +356,8 @@
 	LOGP(DAUC, LOGL_DEBUG, "IMSI='%s': storing %s = %s\n",
 	     subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number",
 	     osmo_quote_str((const char*)luop->peer, -1));
-	if (db_subscr_lu(g_hlr->dbc, subscr->id, (const char *)luop->peer, luop->is_ps))
+	if (db_subscr_lu(g_hlr->dbc, subscr->id, (const char *)luop->peer, luop->is_ps,
+			 gsup->rat_types, gsup->rat_types_len))
 		LOGP(DAUC, LOGL_ERROR, "IMSI='%s': Cannot update %s in the database\n",
 		     subscr->imsi, luop->is_ps ? "SGSN number" : "VLR number");
 
diff --git a/src/hlr_vty_subscr.c b/src/hlr_vty_subscr.c
index 21296ff..983a71c 100644
--- a/src/hlr_vty_subscr.c
+++ b/src/hlr_vty_subscr.c
@@ -80,6 +80,8 @@
 		vty_out(vty, "    PS purged%s", VTY_NEWLINE);
 	if (subscr->last_lu_seen)
 		vty_out(vty, "    last LU seen: %s%s", get_datestr(&subscr->last_lu_seen), VTY_NEWLINE);
+	if (subscr->last_lu_rat[0])
+		vty_out(vty, "    last LU RAT: %s%s", subscr->last_lu_rat, VTY_NEWLINE);
 	for (i = OSMO_RAT_UNKNOWN + 1; i < ARRAY_SIZE(subscr->rat_types); i++) {
 		vty_out(vty, "    %s: %s%s", osmo_rat_type_name(i), subscr->rat_types[i] ? "allowed" : "forbidden",
 			VTY_NEWLINE);
diff --git a/tests/db/db_test.c b/tests/db/db_test.c
index c4ed6ed..81ff312 100644
--- a/tests/db/db_test.c
+++ b/tests/db/db_test.c
@@ -337,39 +337,39 @@
 
 	comment("Record LU for PS and CS (SGSN and VLR names)");
 
-	ASSERT_RC(db_subscr_lu(dbc, id0, "5952", true), 0);
+	ASSERT_RC(db_subscr_lu(dbc, id0, "5952", true, NULL, 0), 0);
 	ASSERT_SEL(id, id0, 0);
-	ASSERT_RC(db_subscr_lu(dbc, id0, "712", false), 0);
+	ASSERT_RC(db_subscr_lu(dbc, id0, "712", false, NULL, 0), 0);
 	ASSERT_SEL(id, id0, 0);
 
 	comment("Record LU for PS and CS (SGSN and VLR names) *again*");
 
-	ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
+	ASSERT_RC(db_subscr_lu(dbc, id0, "111", true, NULL, 0), 0);
 	ASSERT_SEL(id, id0, 0);
-	ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
+	ASSERT_RC(db_subscr_lu(dbc, id0, "111", true, NULL, 0), 0);
 	ASSERT_SEL(id, id0, 0);
-	ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
+	ASSERT_RC(db_subscr_lu(dbc, id0, "222", false, NULL, 0), 0);
 	ASSERT_SEL(id, id0, 0);
-	ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
+	ASSERT_RC(db_subscr_lu(dbc, id0, "222", false, NULL, 0), 0);
 	ASSERT_SEL(id, id0, 0);
 
 	comment("Unset LU info for PS and CS (SGSN and VLR names)");
-	ASSERT_RC(db_subscr_lu(dbc, id0, "", true), 0);
+	ASSERT_RC(db_subscr_lu(dbc, id0, "", true, NULL, 0), 0);
 	ASSERT_SEL(id, id0, 0);
-	ASSERT_RC(db_subscr_lu(dbc, id0, "", false), 0);
+	ASSERT_RC(db_subscr_lu(dbc, id0, "", false, NULL, 0), 0);
 	ASSERT_SEL(id, id0, 0);
 
-	ASSERT_RC(db_subscr_lu(dbc, id0, "111", true), 0);
-	ASSERT_RC(db_subscr_lu(dbc, id0, "222", false), 0);
+	ASSERT_RC(db_subscr_lu(dbc, id0, "111", true, NULL, 0), 0);
+	ASSERT_RC(db_subscr_lu(dbc, id0, "222", false, NULL, 0), 0);
 	ASSERT_SEL(id, id0, 0);
-	ASSERT_RC(db_subscr_lu(dbc, id0, NULL, true), 0);
+	ASSERT_RC(db_subscr_lu(dbc, id0, NULL, true, NULL, 0), 0);
 	ASSERT_SEL(id, id0, 0);
-	ASSERT_RC(db_subscr_lu(dbc, id0, NULL, false), 0);
+	ASSERT_RC(db_subscr_lu(dbc, id0, NULL, false, NULL, 0), 0);
 	ASSERT_SEL(id, id0, 0);
 
 	comment("Record LU for non-existent ID");
-	ASSERT_RC(db_subscr_lu(dbc, 99999, "5952", true), -ENOENT);
-	ASSERT_RC(db_subscr_lu(dbc, 99999, "712", false), -ENOENT);
+	ASSERT_RC(db_subscr_lu(dbc, 99999, "5952", true, NULL, 0), -ENOENT);
+	ASSERT_RC(db_subscr_lu(dbc, 99999, "712", false, NULL, 0), -ENOENT);
 	ASSERT_SEL(id, 99999, -ENOENT);
 
 	comment("Purge and un-purge PS and CS");
diff --git a/tests/db/db_test.err b/tests/db/db_test.err
index 1d34045..faa37b9 100644
--- a/tests/db/db_test.err
+++ b/tests/db/db_test.err
@@ -373,7 +373,7 @@
 
 --- Record LU for PS and CS (SGSN and VLR names)
 
-db_subscr_lu(dbc, id0, "5952", true) --> 0
+db_subscr_lu(dbc, id0, "5952", true, NULL, 0) --> 0
 
 db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
 struct hlr_subscriber {
@@ -383,7 +383,7 @@
   .sgsn_number = '5952',
 }
 
-db_subscr_lu(dbc, id0, "712", false) --> 0
+db_subscr_lu(dbc, id0, "712", false, NULL, 0) --> 0
 
 db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
 struct hlr_subscriber {
@@ -397,7 +397,7 @@
 
 --- Record LU for PS and CS (SGSN and VLR names) *again*
 
-db_subscr_lu(dbc, id0, "111", true) --> 0
+db_subscr_lu(dbc, id0, "111", true, NULL, 0) --> 0
 
 db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
 struct hlr_subscriber {
@@ -408,7 +408,7 @@
   .sgsn_number = '111',
 }
 
-db_subscr_lu(dbc, id0, "111", true) --> 0
+db_subscr_lu(dbc, id0, "111", true, NULL, 0) --> 0
 
 db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
 struct hlr_subscriber {
@@ -419,7 +419,7 @@
   .sgsn_number = '111',
 }
 
-db_subscr_lu(dbc, id0, "222", false) --> 0
+db_subscr_lu(dbc, id0, "222", false, NULL, 0) --> 0
 
 db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
 struct hlr_subscriber {
@@ -430,7 +430,7 @@
   .sgsn_number = '111',
 }
 
-db_subscr_lu(dbc, id0, "222", false) --> 0
+db_subscr_lu(dbc, id0, "222", false, NULL, 0) --> 0
 
 db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
 struct hlr_subscriber {
@@ -444,7 +444,7 @@
 
 --- Unset LU info for PS and CS (SGSN and VLR names)
 
-db_subscr_lu(dbc, id0, "", true) --> 0
+db_subscr_lu(dbc, id0, "", true, NULL, 0) --> 0
 
 db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
 struct hlr_subscriber {
@@ -454,7 +454,7 @@
   .vlr_number = '222',
 }
 
-db_subscr_lu(dbc, id0, "", false) --> 0
+db_subscr_lu(dbc, id0, "", false, NULL, 0) --> 0
 
 db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
 struct hlr_subscriber {
@@ -463,9 +463,9 @@
   .msisdn = '543210123456789',
 }
 
-db_subscr_lu(dbc, id0, "111", true) --> 0
+db_subscr_lu(dbc, id0, "111", true, NULL, 0) --> 0
 
-db_subscr_lu(dbc, id0, "222", false) --> 0
+db_subscr_lu(dbc, id0, "222", false, NULL, 0) --> 0
 
 db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
 struct hlr_subscriber {
@@ -476,7 +476,7 @@
   .sgsn_number = '111',
 }
 
-db_subscr_lu(dbc, id0, NULL, true) --> 0
+db_subscr_lu(dbc, id0, NULL, true, NULL, 0) --> 0
 
 db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
 struct hlr_subscriber {
@@ -486,7 +486,7 @@
   .vlr_number = '222',
 }
 
-db_subscr_lu(dbc, id0, NULL, false) --> 0
+db_subscr_lu(dbc, id0, NULL, false, NULL, 0) --> 0
 
 db_subscr_get_by_id(dbc, id0, &g_subscr) --> 0
 struct hlr_subscriber {
@@ -498,10 +498,10 @@
 
 --- Record LU for non-existent ID
 
-db_subscr_lu(dbc, 99999, "5952", true) --> -ENOENT
+db_subscr_lu(dbc, 99999, "5952", true, NULL, 0) --> -ENOENT
 DAUC Cannot update SGSN number for subscriber ID=99999: no such subscriber
 
-db_subscr_lu(dbc, 99999, "712", false) --> -ENOENT
+db_subscr_lu(dbc, 99999, "712", false, NULL, 0) --> -ENOENT
 DAUC Cannot update VLR number for subscriber ID=99999: no such subscriber
 
 db_subscr_get_by_id(dbc, 99999, &g_subscr) --> -ENOENT