Class SubscriptionStore

java.lang.Object
com.iizix.server.push.engine.store.SubscriptionStore

public final class SubscriptionStore extends Object
The subscription table IZS.SUBSCRIPTIONS: one row per (user, application, Topic, device), carrying the transport and what it needs, plus the device's last-seen address.

The key is (uid, app_id, topic, device_uuid) (GATE 1). The device is in it, so a user holds the same (application, Topic) on as many devices as they subscribe from; "all my devices" is the resolver expanding the key over the device dimension, not a different row. app_id is the validated application name, * for the iiziRun subscriptions of the server, and topic is the empty string for the application-level subscription - both NOT NULL, because a NULL in a unique key is not unique in Derby or PostgreSQL and the previous DDL's NULL conventions made the key unenforceable for exactly the two values the client sends most.

SQL relied on, and its portability. CREATE TABLE with BIGINT, VARCHAR(n), CHAR(36), SMALLINT; a named UNIQUE constraint; a FOREIGN KEY ... ON DELETE CASCADE to IZS.USERS(uid); SELECT, INSERT, UPDATE, DELETE with ? parameters; IN (...) lists; DROP TABLE; DatabaseMetaData.getColumns. All of it is SQL-92 and runs unchanged on Derby (the shipped default) and PostgreSQL (the likely production target). Nothing here needs MERGE, UPSERT, LIMIT, sequences or a vendor type. Identifiers are unquoted, so each database folds them to its own case, which is why the migration probe looks the table up in both.

Migration. ensureSchema() creates the table when it is absent and replaces it when it has the previous shape (aid BIGINT with a foreign key to IZS.APPS that nothing populates, device_uuid CHAR(20) that cannot hold a 36-character UUID, no device in the key). The previous shape's rows are dropped: none can be reached by any client, because no 20-character device UUID matches a 36-character one, and Christopher has said nothing depends on current data. The drop is logged SEVERE with the row count so it never happens unseen.

Thread-safe: every method takes its own connection and commits or rolls back it.

Author:
Christopher Mindus
  • Field Details

  • Constructor Details

    • SubscriptionStore

      public SubscriptionStore(ConnectionSource connections)
      Constructor.
      Parameters:
      connections - The connection source.
  • Method Details

    • ensureSchema

      public boolean ensureSchema() throws SQLException
      Creates the table when absent, replaces it when it has the previous shape.
      Returns:
      true if the table was created or replaced, false if it was already current.
      Throws:
      SQLException - For SQL errors.
    • register

      public SubscriptionStore.RegisterResult register(long uid, String appId, String topic, String deviceUuid, PushTransport transport, String subId, String domain, String userAgent, String remoteIp) throws SQLException
      Registers a subscription: creates the row, or replaces the subscription of an existing key when it changed.
      Parameters:
      uid - The user ID.
      appId - The application ID, * for iiziRun.
      topic - The Topic, empty for the application-level subscription.
      deviceUuid - The device UUID, canonical 36 characters.
      transport - The transport.
      subId - The transport's subscription.
      domain - The host name the client used.
      userAgent - The user agent, null for none.
      remoteIp - The remote address, null when unknown.
      Returns:
      What happened.
      Throws:
      SQLException - For SQL errors.
    • unregister

      public int unregister(long uid, String appId, String topic, String deviceUuid) throws SQLException
      Removes the subscriptions of a device for an application and Topic.
      Parameters:
      uid - The user ID.
      appId - The application ID, null for any.
      topic - The Topic, null for any.
      deviceUuid - The device UUID.
      Returns:
      The number of rows removed.
      Throws:
      SQLException - For SQL errors.
    • unregisterBySubscription

      public int unregisterBySubscription(long uid, String deviceUuid, String subId) throws SQLException
      Removes the subscriptions of a device that carry a given transport subscription: the client's unsubscribe body may carry only the subscription and no application or Topic.
      Parameters:
      uid - The user ID.
      deviceUuid - The device UUID.
      subId - The transport subscription.
      Returns:
      The number of rows removed.
      Throws:
      SQLException - For SQL errors.
    • delete

      public boolean delete(SubscriptionRecord record) throws SQLException
      Removes one subscription by its identity, e.g. after the push service reported it gone.
      Parameters:
      record - The subscription.
      Returns:
      true if a row was removed.
      Throws:
      SQLException - For SQL errors.
    • find

      public SubscriptionRecord find(long uid, String appId, String topic, String deviceUuid) throws SQLException
      Finds one subscription by its key.
      Parameters:
      uid - The user ID.
      appId - The application ID.
      topic - The Topic.
      deviceUuid - The device UUID.
      Returns:
      The subscription, or null.
      Throws:
      SQLException - For SQL errors.
    • query

      public List<SubscriptionRecord> query(long[] uids, List<String> appIds, List<String> topics, List<String> devices) throws SQLException
      Queries subscriptions. An empty or null list means "any" for that dimension; the result is in (uid, app_id, topic, device_uuid) order. Rows whose stored transport ordinal is outside the constant set are skipped and logged.
      Parameters:
      uids - The user IDs, empty or null for any.
      appIds - The application IDs, empty or null for any.
      topics - The Topics, empty or null for any.
      devices - The device UUIDs, empty or null for any.
      Returns:
      The subscriptions.
      Throws:
      SQLException - For SQL errors.
    • count

      public int count(long[] uids, List<String> appIds, List<String> topics, List<String> devices) throws SQLException
      Counts subscriptions with the same dimensions as query(long[], List, List, List).
      Parameters:
      uids - The user IDs, empty or null for any.
      appIds - The application IDs, empty or null for any.
      topics - The Topics, empty or null for any.
      devices - The device UUIDs, empty or null for any.
      Returns:
      The count.
      Throws:
      SQLException - For SQL errors.
    • touch

      public int touch(long uid, String deviceUuid, String remoteIp) throws SQLException
      Records that a device presented itself: updates last_seen and last_ip of every subscription of the device, and clears the resolved region when the address changed so it is resolved again.
      Parameters:
      uid - The user ID.
      deviceUuid - The device UUID.
      remoteIp - The remote address, null when unknown.
      Returns:
      The number of rows touched.
      Throws:
      SQLException - For SQL errors.
    • setRegion

      public void setRegion(long uid, String deviceUuid, String region) throws SQLException
      Stores the region the device's last address resolved to.
      Parameters:
      uid - The user ID.
      deviceUuid - The device UUID.
      region - The region code, null for unknown.
      Throws:
      SQLException - For SQL errors.