SHA256
Добавили счётчик подписчиков в каналах и подписки пользователя
This commit is contained in:
@@ -30,6 +30,8 @@ public final class DatabaseInitializer {
|
||||
public static final int SCHEMA_VERSION_11 = 11;
|
||||
public static final int SCHEMA_VERSION_12 = 12;
|
||||
public static final int SCHEMA_VERSION_13 = 13;
|
||||
public static final int SCHEMA_VERSION_14 = 14;
|
||||
public static final int SCHEMA_VERSION_15 = 15;
|
||||
public static final String POSTGRES_SCHEMA_RESOURCE = "postgres/schema_v1.sql";
|
||||
public static final String POSTGRES_MIGRATION_V2_RESOURCE = "postgres/migration_v2.sql";
|
||||
public static final String POSTGRES_MIGRATION_V3_RESOURCE = "postgres/migration_v3.sql";
|
||||
@@ -43,6 +45,8 @@ public final class DatabaseInitializer {
|
||||
public static final String POSTGRES_MIGRATION_V11_RESOURCE = "postgres/migration_v11.sql";
|
||||
public static final String POSTGRES_MIGRATION_V12_RESOURCE = "postgres/migration_v12.sql";
|
||||
public static final String POSTGRES_MIGRATION_V13_RESOURCE = "postgres/migration_v13.sql";
|
||||
public static final String POSTGRES_MIGRATION_V14_RESOURCE = "postgres/migration_v14.sql";
|
||||
public static final String POSTGRES_MIGRATION_V15_RESOURCE = "postgres/migration_v15.sql";
|
||||
|
||||
private DatabaseInitializer() {}
|
||||
|
||||
@@ -154,6 +158,14 @@ public final class DatabaseInitializer {
|
||||
runSqlScript(conn, POSTGRES_MIGRATION_V13_RESOURCE);
|
||||
currentVersion = SCHEMA_VERSION_13;
|
||||
}
|
||||
if (currentVersion < SCHEMA_VERSION_14) {
|
||||
runSqlScript(conn, POSTGRES_MIGRATION_V14_RESOURCE);
|
||||
currentVersion = SCHEMA_VERSION_14;
|
||||
}
|
||||
if (currentVersion < SCHEMA_VERSION_15) {
|
||||
runSqlScript(conn, POSTGRES_MIGRATION_V15_RESOURCE);
|
||||
currentVersion = SCHEMA_VERSION_15;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+96
@@ -99,6 +99,8 @@ public final class BlockchainResyncCleanupDAO {
|
||||
int deletedBlocks = deleteBlocksForChain(c, blockchainName);
|
||||
int deletedBlockchainState = deleteBlockchainStateForChain(c, blockchainName);
|
||||
|
||||
rebuildStatsState(c);
|
||||
|
||||
c.commit();
|
||||
|
||||
return new CleanupResult(
|
||||
@@ -366,6 +368,100 @@ public final class BlockchainResyncCleanupDAO {
|
||||
""", blockchainName);
|
||||
}
|
||||
|
||||
private void rebuildStatsState(Connection c) throws SQLException {
|
||||
try (PreparedStatement truncate = c.prepareStatement("""
|
||||
TRUNCATE TABLE user_stats_state, channel_stats_state
|
||||
""")) {
|
||||
truncate.executeUpdate();
|
||||
}
|
||||
|
||||
try (PreparedStatement ps = c.prepareStatement("""
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
)
|
||||
SELECT
|
||||
u.login,
|
||||
COALESCE(own.owned_public_channels_count, 0),
|
||||
COALESCE(fu.following_users_count, 0),
|
||||
COALESCE(fc.following_channels_count, 0),
|
||||
COALESCE(cf.close_friends_count, 0),
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
FROM solana_user_pda_current u
|
||||
LEFT JOIN (
|
||||
SELECT owner_login, COUNT(*)::INTEGER AS owned_public_channels_count
|
||||
FROM channel_names_state
|
||||
WHERE channel_type_code = 1
|
||||
GROUP BY owner_login
|
||||
) own ON LOWER(own.owner_login) = LOWER(u.login)
|
||||
LEFT JOIN (
|
||||
SELECT login, COUNT(*)::INTEGER AS following_users_count
|
||||
FROM connections_state
|
||||
WHERE rel_type = 30
|
||||
AND to_block_number = 0
|
||||
GROUP BY login
|
||||
) fu ON LOWER(fu.login) = LOWER(u.login)
|
||||
LEFT JOIN (
|
||||
SELECT cs.login, COUNT(*)::INTEGER AS following_channels_count
|
||||
FROM connections_state cs
|
||||
JOIN channel_names_state cn
|
||||
ON cn.owner_bch_name = cs.to_bch_name
|
||||
AND cn.channel_root_block_number = cs.to_block_number
|
||||
AND cn.channel_root_block_hash = cs.to_block_hash
|
||||
WHERE cs.rel_type = 30
|
||||
AND cn.channel_type_code = 1
|
||||
GROUP BY cs.login
|
||||
) fc ON LOWER(fc.login) = LOWER(u.login)
|
||||
LEFT JOIN (
|
||||
SELECT login, COUNT(*)::INTEGER AS close_friends_count
|
||||
FROM connections_state
|
||||
WHERE rel_type = 10
|
||||
GROUP BY login
|
||||
) cf ON LOWER(cf.login) = LOWER(u.login)
|
||||
""")) {
|
||||
ps.executeUpdate();
|
||||
}
|
||||
|
||||
try (PreparedStatement ps = c.prepareStatement("""
|
||||
INSERT INTO channel_stats_state (
|
||||
owner_bch_name,
|
||||
channel_root_block_number,
|
||||
channel_root_block_hash,
|
||||
owner_login,
|
||||
channel_type_code,
|
||||
subscribers_count,
|
||||
updated_at_ms
|
||||
)
|
||||
SELECT
|
||||
cn.owner_bch_name,
|
||||
cn.channel_root_block_number,
|
||||
cn.channel_root_block_hash,
|
||||
cn.owner_login,
|
||||
cn.channel_type_code,
|
||||
COUNT(DISTINCT cs.login)::INTEGER AS subscribers_count,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
FROM channel_names_state cn
|
||||
LEFT JOIN connections_state cs
|
||||
ON cs.rel_type = 30
|
||||
AND cs.to_bch_name = cn.owner_bch_name
|
||||
AND cs.to_block_number = cn.channel_root_block_number
|
||||
AND cs.to_block_hash = cn.channel_root_block_hash
|
||||
WHERE cn.channel_type_code = 1
|
||||
GROUP BY
|
||||
cn.owner_bch_name,
|
||||
cn.channel_root_block_number,
|
||||
cn.channel_root_block_hash,
|
||||
cn.owner_login,
|
||||
cn.channel_type_code
|
||||
""")) {
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
private int executeDelete(Connection c, String sql, String value) throws SQLException {
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, value);
|
||||
|
||||
@@ -0,0 +1,620 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_stats_state (
|
||||
login TEXT PRIMARY KEY REFERENCES solana_user_pda_current(login) ON DELETE CASCADE,
|
||||
owned_public_channels_count INTEGER NOT NULL DEFAULT 0 CHECK (owned_public_channels_count >= 0),
|
||||
following_users_count INTEGER NOT NULL DEFAULT 0 CHECK (following_users_count >= 0),
|
||||
following_channels_count INTEGER NOT NULL DEFAULT 0 CHECK (following_channels_count >= 0),
|
||||
close_friends_count INTEGER NOT NULL DEFAULT 0 CHECK (close_friends_count >= 0),
|
||||
updated_at_ms BIGINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_stats_state_following_users_count
|
||||
ON user_stats_state (following_users_count);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_stats_state (
|
||||
owner_bch_name TEXT NOT NULL,
|
||||
channel_root_block_number INTEGER NOT NULL CHECK (channel_root_block_number >= 0),
|
||||
channel_root_block_hash BYTEA NOT NULL,
|
||||
owner_login TEXT NOT NULL,
|
||||
channel_type_code INTEGER NOT NULL DEFAULT 1,
|
||||
subscribers_count INTEGER NOT NULL DEFAULT 0 CHECK (subscribers_count >= 0),
|
||||
updated_at_ms BIGINT NOT NULL,
|
||||
PRIMARY KEY (owner_bch_name, channel_root_block_number, channel_root_block_hash)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_channel_stats_state_owner_login
|
||||
ON channel_stats_state (owner_login);
|
||||
|
||||
CREATE OR REPLACE FUNCTION shine_user_stats_state_ai()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
now_ms BIGINT;
|
||||
BEGIN
|
||||
now_ms := CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT);
|
||||
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
now_ms
|
||||
)
|
||||
ON CONFLICT (login) DO NOTHING;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION shine_channel_names_state_stats_ai()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
now_ms BIGINT;
|
||||
public_subscribers_count INTEGER;
|
||||
BEGIN
|
||||
now_ms := CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT);
|
||||
|
||||
IF NEW.owner_login IS NULL OR btrim(NEW.owner_login) = '' THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
IF NEW.channel_type_code = 1 THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.owner_login,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
now_ms
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
owned_public_channels_count = user_stats_state.owned_public_channels_count + 1,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
|
||||
SELECT COUNT(*)::INTEGER
|
||||
INTO public_subscribers_count
|
||||
FROM connections_state cs
|
||||
JOIN channel_names_state cn
|
||||
ON cn.owner_bch_name = cs.to_bch_name
|
||||
AND cn.channel_root_block_number = cs.to_block_number
|
||||
AND cn.channel_root_block_hash = cs.to_block_hash
|
||||
WHERE cs.rel_type = 30
|
||||
AND cn.owner_bch_name = NEW.owner_bch_name
|
||||
AND cn.channel_root_block_number = NEW.channel_root_block_number
|
||||
AND cn.channel_root_block_hash = NEW.channel_root_block_hash
|
||||
AND cn.channel_type_code = 1;
|
||||
|
||||
INSERT INTO channel_stats_state (
|
||||
owner_bch_name,
|
||||
channel_root_block_number,
|
||||
channel_root_block_hash,
|
||||
owner_login,
|
||||
channel_type_code,
|
||||
subscribers_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.owner_bch_name,
|
||||
NEW.channel_root_block_number,
|
||||
NEW.channel_root_block_hash,
|
||||
NEW.owner_login,
|
||||
NEW.channel_type_code,
|
||||
COALESCE(public_subscribers_count, 0),
|
||||
now_ms
|
||||
)
|
||||
ON CONFLICT (owner_bch_name, channel_root_block_number, channel_root_block_hash) DO UPDATE SET
|
||||
owner_login = EXCLUDED.owner_login,
|
||||
channel_type_code = EXCLUDED.channel_type_code,
|
||||
subscribers_count = EXCLUDED.subscribers_count,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSE
|
||||
WITH pending AS (
|
||||
SELECT cs.login, COUNT(*)::INTEGER AS cnt
|
||||
FROM connections_state cs
|
||||
WHERE cs.rel_type = 30
|
||||
AND cs.to_bch_name = NEW.owner_bch_name
|
||||
AND cs.to_block_number = NEW.channel_root_block_number
|
||||
AND cs.to_block_hash = NEW.channel_root_block_hash
|
||||
GROUP BY cs.login
|
||||
)
|
||||
UPDATE user_stats_state us
|
||||
SET following_channels_count = GREATEST(0, us.following_channels_count - pending.cnt),
|
||||
updated_at_ms = now_ms
|
||||
FROM pending
|
||||
WHERE us.login = pending.login;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION shine_blocks_connection_state_ai()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
resolved_login TEXT;
|
||||
positive_rel_type INTEGER;
|
||||
existed_before BOOLEAN;
|
||||
target_channel_type INTEGER;
|
||||
BEGIN
|
||||
IF NEW.msg_type <> 3 THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
resolved_login := shine_resolve_login(NEW.to_login, NEW.to_bch_name);
|
||||
|
||||
IF NEW.msg_sub_type IN (10, 20, 30, 40, 50, 52, 54, 60, 70, 74) THEN
|
||||
IF resolved_login IS NULL OR NEW.to_bch_name IS NULL THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM connections_state
|
||||
WHERE login = NEW.login
|
||||
AND rel_type = NEW.msg_sub_type
|
||||
AND to_login = resolved_login
|
||||
AND to_bch_name = NEW.to_bch_name
|
||||
AND to_block_number = COALESCE(NEW.to_block_number, 0)
|
||||
AND to_block_hash = COALESCE(NEW.to_block_hash, decode(repeat('00', 32), 'hex'))
|
||||
)
|
||||
INTO existed_before;
|
||||
|
||||
IF NOT existed_before THEN
|
||||
IF NEW.msg_sub_type = 10 THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
close_friends_count = user_stats_state.close_friends_count + 1,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSIF NEW.msg_sub_type = 30 THEN
|
||||
IF NEW.to_block_number IS NULL THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_channels_count = user_stats_state.following_channels_count + 1,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSIF NEW.to_block_number = 0 THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_users_count = user_stats_state.following_users_count + 1,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSE
|
||||
SELECT cn.channel_type_code
|
||||
INTO target_channel_type
|
||||
FROM channel_names_state cn
|
||||
WHERE cn.owner_bch_name = NEW.to_bch_name
|
||||
AND cn.channel_root_block_number = NEW.to_block_number
|
||||
AND cn.channel_root_block_hash = NEW.to_block_hash
|
||||
LIMIT 1;
|
||||
|
||||
IF target_channel_type = 1 THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_channels_count = user_stats_state.following_channels_count + 1,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
|
||||
INSERT INTO channel_stats_state (
|
||||
owner_bch_name,
|
||||
channel_root_block_number,
|
||||
channel_root_block_hash,
|
||||
owner_login,
|
||||
channel_type_code,
|
||||
subscribers_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.to_bch_name,
|
||||
NEW.to_block_number,
|
||||
NEW.to_block_hash,
|
||||
resolved_login,
|
||||
target_channel_type,
|
||||
1,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (owner_bch_name, channel_root_block_number, channel_root_block_hash) DO UPDATE SET
|
||||
owner_login = EXCLUDED.owner_login,
|
||||
channel_type_code = EXCLUDED.channel_type_code,
|
||||
subscribers_count = channel_stats_state.subscribers_count + 1,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSIF target_channel_type IS NULL THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_channels_count = user_stats_state.following_channels_count + 1,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
DELETE FROM connections_state
|
||||
WHERE login = NEW.login
|
||||
AND rel_type = NEW.msg_sub_type
|
||||
AND to_login = resolved_login
|
||||
AND to_bch_name = NEW.to_bch_name
|
||||
AND to_block_number = COALESCE(NEW.to_block_number, 0)
|
||||
AND to_block_hash = COALESCE(NEW.to_block_hash, decode(repeat('00', 32), 'hex'));
|
||||
|
||||
INSERT INTO connections_state (
|
||||
login, rel_type, to_login, to_bch_name, to_block_number, to_block_hash
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
NEW.msg_sub_type,
|
||||
resolved_login,
|
||||
NEW.to_bch_name,
|
||||
COALESCE(NEW.to_block_number, 0),
|
||||
COALESCE(NEW.to_block_hash, decode(repeat('00', 32), 'hex'))
|
||||
);
|
||||
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
positive_rel_type := CASE NEW.msg_sub_type
|
||||
WHEN 11 THEN 10
|
||||
WHEN 21 THEN 20
|
||||
WHEN 31 THEN 30
|
||||
WHEN 41 THEN 40
|
||||
WHEN 51 THEN 50
|
||||
WHEN 53 THEN 52
|
||||
WHEN 55 THEN 54
|
||||
WHEN 61 THEN 60
|
||||
WHEN 71 THEN 70
|
||||
WHEN 75 THEN 74
|
||||
ELSE NULL
|
||||
END;
|
||||
|
||||
IF positive_rel_type IS NULL OR resolved_login IS NULL THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM connections_state
|
||||
WHERE login = NEW.login
|
||||
AND rel_type = positive_rel_type
|
||||
AND to_login = resolved_login
|
||||
AND to_bch_name = NEW.to_bch_name
|
||||
AND to_block_number = COALESCE(NEW.to_block_number, 0)
|
||||
AND to_block_hash = COALESCE(NEW.to_block_hash, decode(repeat('00', 32), 'hex'))
|
||||
)
|
||||
INTO existed_before;
|
||||
|
||||
IF NOT existed_before THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
IF positive_rel_type = 10 THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
close_friends_count = GREATEST(0, user_stats_state.close_friends_count - 1),
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSIF positive_rel_type = 30 THEN
|
||||
IF NEW.to_block_number IS NULL THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_channels_count = GREATEST(0, user_stats_state.following_channels_count - 1),
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSIF NEW.to_block_number = 0 THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_users_count = GREATEST(0, user_stats_state.following_users_count - 1),
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSE
|
||||
SELECT cn.channel_type_code
|
||||
INTO target_channel_type
|
||||
FROM channel_names_state cn
|
||||
WHERE cn.owner_bch_name = NEW.to_bch_name
|
||||
AND cn.channel_root_block_number = NEW.to_block_number
|
||||
AND cn.channel_root_block_hash = NEW.to_block_hash
|
||||
LIMIT 1;
|
||||
|
||||
IF target_channel_type = 1 THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_channels_count = GREATEST(0, user_stats_state.following_channels_count - 1),
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
|
||||
INSERT INTO channel_stats_state (
|
||||
owner_bch_name,
|
||||
channel_root_block_number,
|
||||
channel_root_block_hash,
|
||||
owner_login,
|
||||
channel_type_code,
|
||||
subscribers_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.to_bch_name,
|
||||
NEW.to_block_number,
|
||||
NEW.to_block_hash,
|
||||
resolved_login,
|
||||
target_channel_type,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (owner_bch_name, channel_root_block_number, channel_root_block_hash) DO UPDATE SET
|
||||
owner_login = EXCLUDED.owner_login,
|
||||
channel_type_code = EXCLUDED.channel_type_code,
|
||||
subscribers_count = GREATEST(0, channel_stats_state.subscribers_count - 1),
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSIF target_channel_type IS NULL THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_channels_count = GREATEST(0, user_stats_state.following_channels_count - 1),
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
DELETE FROM connections_state
|
||||
WHERE login = NEW.login
|
||||
AND rel_type = positive_rel_type
|
||||
AND to_login = resolved_login
|
||||
AND to_bch_name = NEW.to_bch_name
|
||||
AND to_block_number = COALESCE(NEW.to_block_number, 0)
|
||||
AND to_block_hash = COALESCE(NEW.to_block_hash, decode(repeat('00', 32), 'hex'));
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_user_stats_state_ai ON solana_user_pda_current;
|
||||
CREATE TRIGGER trg_user_stats_state_ai
|
||||
AFTER INSERT ON solana_user_pda_current
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION shine_user_stats_state_ai();
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_channel_names_state_stats_ai ON channel_names_state;
|
||||
CREATE TRIGGER trg_channel_names_state_stats_ai
|
||||
AFTER INSERT ON channel_names_state
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION shine_channel_names_state_stats_ai();
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_blocks_connection_state_ai ON blocks;
|
||||
CREATE TRIGGER trg_blocks_connection_state_ai
|
||||
AFTER INSERT ON blocks
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION shine_blocks_connection_state_ai();
|
||||
|
||||
TRUNCATE TABLE user_stats_state, channel_stats_state;
|
||||
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
)
|
||||
SELECT
|
||||
u.login,
|
||||
COALESCE(own.owned_public_channels_count, 0) AS owned_public_channels_count,
|
||||
COALESCE(fu.following_users_count, 0) AS following_users_count,
|
||||
COALESCE(fc.following_channels_count, 0) AS following_channels_count,
|
||||
COALESCE(cf.close_friends_count, 0) AS close_friends_count,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT) AS updated_at_ms
|
||||
FROM solana_user_pda_current u
|
||||
LEFT JOIN (
|
||||
SELECT owner_login, COUNT(*)::INTEGER AS owned_public_channels_count
|
||||
FROM channel_names_state
|
||||
WHERE channel_type_code = 1
|
||||
GROUP BY owner_login
|
||||
) own ON LOWER(own.owner_login) = LOWER(u.login)
|
||||
LEFT JOIN (
|
||||
SELECT login, COUNT(*)::INTEGER AS following_users_count
|
||||
FROM connections_state
|
||||
WHERE rel_type = 30
|
||||
AND to_block_number = 0
|
||||
GROUP BY login
|
||||
) fu ON LOWER(fu.login) = LOWER(u.login)
|
||||
LEFT JOIN (
|
||||
SELECT cs.login, COUNT(*)::INTEGER AS following_channels_count
|
||||
FROM connections_state cs
|
||||
JOIN channel_names_state cn
|
||||
ON cn.owner_bch_name = cs.to_bch_name
|
||||
AND cn.channel_root_block_number = cs.to_block_number
|
||||
AND cn.channel_root_block_hash = cs.to_block_hash
|
||||
WHERE cs.rel_type = 30
|
||||
AND cn.channel_type_code = 1
|
||||
GROUP BY cs.login
|
||||
) fc ON LOWER(fc.login) = LOWER(u.login)
|
||||
LEFT JOIN (
|
||||
SELECT login, COUNT(*)::INTEGER AS close_friends_count
|
||||
FROM connections_state
|
||||
WHERE rel_type = 10
|
||||
GROUP BY login
|
||||
) cf ON LOWER(cf.login) = LOWER(u.login);
|
||||
|
||||
INSERT INTO channel_stats_state (
|
||||
owner_bch_name,
|
||||
channel_root_block_number,
|
||||
channel_root_block_hash,
|
||||
owner_login,
|
||||
channel_type_code,
|
||||
subscribers_count,
|
||||
updated_at_ms
|
||||
)
|
||||
SELECT
|
||||
cn.owner_bch_name,
|
||||
cn.channel_root_block_number,
|
||||
cn.channel_root_block_hash,
|
||||
cn.owner_login,
|
||||
cn.channel_type_code,
|
||||
COUNT(DISTINCT cs.login)::INTEGER AS subscribers_count,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT) AS updated_at_ms
|
||||
FROM channel_names_state cn
|
||||
LEFT JOIN connections_state cs
|
||||
ON cs.rel_type = 30
|
||||
AND cs.to_bch_name = cn.owner_bch_name
|
||||
AND cs.to_block_number = cn.channel_root_block_number
|
||||
AND cs.to_block_hash = cn.channel_root_block_hash
|
||||
WHERE cn.channel_type_code = 1
|
||||
GROUP BY
|
||||
cn.owner_bch_name,
|
||||
cn.channel_root_block_number,
|
||||
cn.channel_root_block_hash,
|
||||
cn.owner_login,
|
||||
cn.channel_type_code;
|
||||
|
||||
INSERT INTO db_schema_version (id, schema_version, updated_at_ms)
|
||||
VALUES (1, 14, CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT))
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
schema_version = EXCLUDED.schema_version,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,105 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE OR REPLACE FUNCTION shine_channel_names_state_stats_ai()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
now_ms BIGINT;
|
||||
public_subscribers_count INTEGER;
|
||||
BEGIN
|
||||
now_ms := CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT);
|
||||
|
||||
IF NEW.owner_login IS NULL OR btrim(NEW.owner_login) = '' THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
IF NEW.channel_type_code = 1 THEN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM solana_user_pda_current su
|
||||
WHERE su.login = NEW.owner_login
|
||||
) THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.owner_login,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
now_ms
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
owned_public_channels_count = user_stats_state.owned_public_channels_count + 1,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*)::INTEGER
|
||||
INTO public_subscribers_count
|
||||
FROM connections_state cs
|
||||
JOIN channel_names_state cn
|
||||
ON cn.owner_bch_name = cs.to_bch_name
|
||||
AND cn.channel_root_block_number = cs.to_block_number
|
||||
AND cn.channel_root_block_hash = cs.to_block_hash
|
||||
WHERE cs.rel_type = 30
|
||||
AND cn.owner_bch_name = NEW.owner_bch_name
|
||||
AND cn.channel_root_block_number = NEW.channel_root_block_number
|
||||
AND cn.channel_root_block_hash = NEW.channel_root_block_hash
|
||||
AND cn.channel_type_code = 1;
|
||||
|
||||
INSERT INTO channel_stats_state (
|
||||
owner_bch_name,
|
||||
channel_root_block_number,
|
||||
channel_root_block_hash,
|
||||
owner_login,
|
||||
channel_type_code,
|
||||
subscribers_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.owner_bch_name,
|
||||
NEW.channel_root_block_number,
|
||||
NEW.channel_root_block_hash,
|
||||
NEW.owner_login,
|
||||
NEW.channel_type_code,
|
||||
COALESCE(public_subscribers_count, 0),
|
||||
now_ms
|
||||
)
|
||||
ON CONFLICT (owner_bch_name, channel_root_block_number, channel_root_block_hash) DO UPDATE SET
|
||||
owner_login = EXCLUDED.owner_login,
|
||||
channel_type_code = EXCLUDED.channel_type_code,
|
||||
subscribers_count = EXCLUDED.subscribers_count,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSE
|
||||
WITH pending AS (
|
||||
SELECT cs.login, COUNT(*)::INTEGER AS cnt
|
||||
FROM connections_state cs
|
||||
WHERE cs.rel_type = 30
|
||||
AND cs.to_bch_name = NEW.owner_bch_name
|
||||
AND cs.to_block_number = NEW.channel_root_block_number
|
||||
AND cs.to_block_hash = NEW.channel_root_block_hash
|
||||
GROUP BY cs.login
|
||||
)
|
||||
UPDATE user_stats_state us
|
||||
SET following_channels_count = GREATEST(0, us.following_channels_count - pending.cnt),
|
||||
updated_at_ms = now_ms
|
||||
FROM pending
|
||||
WHERE us.login = pending.login;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
INSERT INTO db_schema_version (id, schema_version, updated_at_ms)
|
||||
VALUES (1, 15, CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT))
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
schema_version = EXCLUDED.schema_version,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
|
||||
COMMIT;
|
||||
@@ -291,6 +291,12 @@ AFTER TRUNCATE ON solana_user_pda_current
|
||||
FOR EACH STATEMENT
|
||||
EXECUTE FUNCTION trg_refresh_user_access_servers_from_user_pda_truncate();
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_user_stats_state_ai ON solana_user_pda_current;
|
||||
CREATE TRIGGER trg_user_stats_state_ai
|
||||
AFTER INSERT ON solana_user_pda_current
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION shine_user_stats_state_ai();
|
||||
|
||||
CREATE TABLE IF NOT EXISTS solana_user_pda_history (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
tx_signature TEXT NOT NULL,
|
||||
@@ -605,6 +611,32 @@ CREATE UNIQUE INDEX IF NOT EXISTS uq_channel_names_state_target
|
||||
CREATE INDEX IF NOT EXISTS idx_channel_names_state_owner
|
||||
ON channel_names_state (owner_login, owner_bch_name);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_stats_state (
|
||||
login TEXT PRIMARY KEY REFERENCES solana_user_pda_current(login) ON DELETE CASCADE,
|
||||
owned_public_channels_count INTEGER NOT NULL DEFAULT 0 CHECK (owned_public_channels_count >= 0),
|
||||
following_users_count INTEGER NOT NULL DEFAULT 0 CHECK (following_users_count >= 0),
|
||||
following_channels_count INTEGER NOT NULL DEFAULT 0 CHECK (following_channels_count >= 0),
|
||||
close_friends_count INTEGER NOT NULL DEFAULT 0 CHECK (close_friends_count >= 0),
|
||||
updated_at_ms BIGINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_stats_state_following_users_count
|
||||
ON user_stats_state (following_users_count);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS channel_stats_state (
|
||||
owner_bch_name TEXT NOT NULL,
|
||||
channel_root_block_number INTEGER NOT NULL CHECK (channel_root_block_number >= 0),
|
||||
channel_root_block_hash BYTEA NOT NULL,
|
||||
owner_login TEXT NOT NULL,
|
||||
channel_type_code INTEGER NOT NULL DEFAULT 1,
|
||||
subscribers_count INTEGER NOT NULL DEFAULT 0 CHECK (subscribers_count >= 0),
|
||||
updated_at_ms BIGINT NOT NULL,
|
||||
PRIMARY KEY (owner_bch_name, channel_root_block_number, channel_root_block_hash)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_channel_stats_state_owner_login
|
||||
ON channel_stats_state (owner_login);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS chat200_state (
|
||||
owner_login TEXT NOT NULL,
|
||||
owner_bch_name TEXT NOT NULL,
|
||||
@@ -921,6 +953,132 @@ BEGIN
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION shine_user_stats_state_ai()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
now_ms BIGINT;
|
||||
BEGIN
|
||||
now_ms := CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT);
|
||||
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
now_ms
|
||||
)
|
||||
ON CONFLICT (login) DO NOTHING;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION shine_channel_names_state_stats_ai()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
now_ms BIGINT;
|
||||
public_subscribers_count INTEGER;
|
||||
BEGIN
|
||||
now_ms := CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT);
|
||||
|
||||
IF NEW.owner_login IS NULL OR btrim(NEW.owner_login) = '' THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
IF NEW.channel_type_code = 1 THEN
|
||||
IF EXISTS (
|
||||
SELECT 1
|
||||
FROM solana_user_pda_current su
|
||||
WHERE su.login = NEW.owner_login
|
||||
) THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.owner_login,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
now_ms
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
owned_public_channels_count = user_stats_state.owned_public_channels_count + 1,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
END IF;
|
||||
|
||||
SELECT COUNT(*)::INTEGER
|
||||
INTO public_subscribers_count
|
||||
FROM connections_state cs
|
||||
JOIN channel_names_state cn
|
||||
ON cn.owner_bch_name = cs.to_bch_name
|
||||
AND cn.channel_root_block_number = cs.to_block_number
|
||||
AND cn.channel_root_block_hash = cs.to_block_hash
|
||||
WHERE cs.rel_type = 30
|
||||
AND cn.owner_bch_name = NEW.owner_bch_name
|
||||
AND cn.channel_root_block_number = NEW.channel_root_block_number
|
||||
AND cn.channel_root_block_hash = NEW.channel_root_block_hash
|
||||
AND cn.channel_type_code = 1;
|
||||
|
||||
INSERT INTO channel_stats_state (
|
||||
owner_bch_name,
|
||||
channel_root_block_number,
|
||||
channel_root_block_hash,
|
||||
owner_login,
|
||||
channel_type_code,
|
||||
subscribers_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.owner_bch_name,
|
||||
NEW.channel_root_block_number,
|
||||
NEW.channel_root_block_hash,
|
||||
NEW.owner_login,
|
||||
NEW.channel_type_code,
|
||||
COALESCE(public_subscribers_count, 0),
|
||||
now_ms
|
||||
)
|
||||
ON CONFLICT (owner_bch_name, channel_root_block_number, channel_root_block_hash) DO UPDATE SET
|
||||
owner_login = EXCLUDED.owner_login,
|
||||
channel_type_code = EXCLUDED.channel_type_code,
|
||||
subscribers_count = EXCLUDED.subscribers_count,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSE
|
||||
WITH pending AS (
|
||||
SELECT cs.login, COUNT(*)::INTEGER AS cnt
|
||||
FROM connections_state cs
|
||||
WHERE cs.rel_type = 30
|
||||
AND cs.to_bch_name = NEW.owner_bch_name
|
||||
AND cs.to_block_number = NEW.channel_root_block_number
|
||||
AND cs.to_block_hash = NEW.channel_root_block_hash
|
||||
GROUP BY cs.login
|
||||
)
|
||||
UPDATE user_stats_state us
|
||||
SET following_channels_count = GREATEST(0, us.following_channels_count - pending.cnt),
|
||||
updated_at_ms = now_ms
|
||||
FROM pending
|
||||
WHERE us.login = pending.login;
|
||||
END IF;
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
CREATE OR REPLACE FUNCTION shine_blocks_line_integrity_bi()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE plpgsql
|
||||
@@ -999,6 +1157,8 @@ AS $$
|
||||
DECLARE
|
||||
resolved_login TEXT;
|
||||
positive_rel_type INTEGER;
|
||||
existed_before BOOLEAN;
|
||||
target_channel_type INTEGER;
|
||||
BEGIN
|
||||
IF NEW.msg_type <> 3 THEN
|
||||
RETURN NEW;
|
||||
@@ -1011,10 +1171,159 @@ BEGIN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM connections_state
|
||||
WHERE login = NEW.login
|
||||
AND rel_type = NEW.msg_sub_type
|
||||
AND to_login = resolved_login
|
||||
AND to_bch_name = NEW.to_bch_name
|
||||
AND to_block_number = COALESCE(NEW.to_block_number, 0)
|
||||
AND to_block_hash = COALESCE(NEW.to_block_hash, decode(repeat('00', 32), 'hex'))
|
||||
)
|
||||
INTO existed_before;
|
||||
|
||||
IF NOT existed_before THEN
|
||||
IF NEW.msg_sub_type = 10 THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
close_friends_count = user_stats_state.close_friends_count + 1,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSIF NEW.msg_sub_type = 30 THEN
|
||||
IF NEW.to_block_number IS NULL THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_channels_count = user_stats_state.following_channels_count + 1,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSIF NEW.to_block_number = 0 THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_users_count = user_stats_state.following_users_count + 1,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSE
|
||||
SELECT cn.channel_type_code
|
||||
INTO target_channel_type
|
||||
FROM channel_names_state cn
|
||||
WHERE cn.owner_bch_name = NEW.to_bch_name
|
||||
AND cn.channel_root_block_number = NEW.to_block_number
|
||||
AND cn.channel_root_block_hash = NEW.to_block_hash
|
||||
LIMIT 1;
|
||||
|
||||
IF target_channel_type = 1 THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_channels_count = user_stats_state.following_channels_count + 1,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
|
||||
INSERT INTO channel_stats_state (
|
||||
owner_bch_name,
|
||||
channel_root_block_number,
|
||||
channel_root_block_hash,
|
||||
owner_login,
|
||||
channel_type_code,
|
||||
subscribers_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.to_bch_name,
|
||||
NEW.to_block_number,
|
||||
NEW.to_block_hash,
|
||||
resolved_login,
|
||||
target_channel_type,
|
||||
1,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (owner_bch_name, channel_root_block_number, channel_root_block_hash) DO UPDATE SET
|
||||
owner_login = EXCLUDED.owner_login,
|
||||
channel_type_code = EXCLUDED.channel_type_code,
|
||||
subscribers_count = channel_stats_state.subscribers_count + 1,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSIF target_channel_type IS NULL THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
1,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_channels_count = user_stats_state.following_channels_count + 1,
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
DELETE FROM connections_state
|
||||
WHERE login = NEW.login
|
||||
AND rel_type = NEW.msg_sub_type
|
||||
AND to_login = resolved_login;
|
||||
AND to_login = resolved_login
|
||||
AND to_bch_name = NEW.to_bch_name
|
||||
AND to_block_number = COALESCE(NEW.to_block_number, 0)
|
||||
AND to_block_hash = COALESCE(NEW.to_block_hash, decode(repeat('00', 32), 'hex'));
|
||||
|
||||
INSERT INTO connections_state (
|
||||
login, rel_type, to_login, to_bch_name, to_block_number, to_block_hash
|
||||
@@ -1048,10 +1357,161 @@ BEGIN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
SELECT EXISTS (
|
||||
SELECT 1
|
||||
FROM connections_state
|
||||
WHERE login = NEW.login
|
||||
AND rel_type = positive_rel_type
|
||||
AND to_login = resolved_login
|
||||
AND to_bch_name = NEW.to_bch_name
|
||||
AND to_block_number = COALESCE(NEW.to_block_number, 0)
|
||||
AND to_block_hash = COALESCE(NEW.to_block_hash, decode(repeat('00', 32), 'hex'))
|
||||
)
|
||||
INTO existed_before;
|
||||
|
||||
IF NOT existed_before THEN
|
||||
RETURN NEW;
|
||||
END IF;
|
||||
|
||||
IF positive_rel_type = 10 THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
close_friends_count = GREATEST(0, user_stats_state.close_friends_count - 1),
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSIF positive_rel_type = 30 THEN
|
||||
IF NEW.to_block_number IS NULL THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_channels_count = GREATEST(0, user_stats_state.following_channels_count - 1),
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSIF NEW.to_block_number = 0 THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_users_count = GREATEST(0, user_stats_state.following_users_count - 1),
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSE
|
||||
SELECT cn.channel_type_code
|
||||
INTO target_channel_type
|
||||
FROM channel_names_state cn
|
||||
WHERE cn.owner_bch_name = NEW.to_bch_name
|
||||
AND cn.channel_root_block_number = NEW.to_block_number
|
||||
AND cn.channel_root_block_hash = NEW.to_block_hash
|
||||
LIMIT 1;
|
||||
|
||||
IF target_channel_type = 1 THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_channels_count = GREATEST(0, user_stats_state.following_channels_count - 1),
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
|
||||
INSERT INTO channel_stats_state (
|
||||
owner_bch_name,
|
||||
channel_root_block_number,
|
||||
channel_root_block_hash,
|
||||
owner_login,
|
||||
channel_type_code,
|
||||
subscribers_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.to_bch_name,
|
||||
NEW.to_block_number,
|
||||
NEW.to_block_hash,
|
||||
resolved_login,
|
||||
target_channel_type,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (owner_bch_name, channel_root_block_number, channel_root_block_hash) DO UPDATE SET
|
||||
owner_login = EXCLUDED.owner_login,
|
||||
channel_type_code = EXCLUDED.channel_type_code,
|
||||
subscribers_count = GREATEST(0, channel_stats_state.subscribers_count - 1),
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
ELSIF target_channel_type IS NULL THEN
|
||||
INSERT INTO user_stats_state (
|
||||
login,
|
||||
owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count,
|
||||
updated_at_ms
|
||||
) VALUES (
|
||||
NEW.login,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
CAST(EXTRACT(EPOCH FROM clock_timestamp()) * 1000 AS BIGINT)
|
||||
)
|
||||
ON CONFLICT (login) DO UPDATE SET
|
||||
following_channels_count = GREATEST(0, user_stats_state.following_channels_count - 1),
|
||||
updated_at_ms = EXCLUDED.updated_at_ms;
|
||||
END IF;
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
DELETE FROM connections_state
|
||||
WHERE login = NEW.login
|
||||
AND rel_type = positive_rel_type
|
||||
AND to_login = resolved_login;
|
||||
AND to_login = resolved_login
|
||||
AND to_bch_name = NEW.to_bch_name
|
||||
AND to_block_number = COALESCE(NEW.to_block_number, 0)
|
||||
AND to_block_hash = COALESCE(NEW.to_block_hash, decode(repeat('00', 32), 'hex'));
|
||||
|
||||
RETURN NEW;
|
||||
END;
|
||||
@@ -1231,4 +1691,10 @@ AFTER INSERT ON blocks
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION shine_blocks_edit_apply_ai();
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_channel_names_state_stats_ai ON channel_names_state;
|
||||
CREATE TRIGGER trg_channel_names_state_stats_ai
|
||||
AFTER INSERT ON channel_names_state
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION shine_channel_names_state_stats_ai();
|
||||
|
||||
COMMIT;
|
||||
|
||||
+27
@@ -16,6 +16,8 @@ import utils.blockchain.BlockchainNameUtil;
|
||||
import blockchain.body.CreateChannelBody;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@@ -65,6 +67,7 @@ public class Net_GetChannelMessages_Handler implements JsonMessageHandler {
|
||||
channel.setMetaUpdatedAtMs(meta.metaUpdatedAtMs);
|
||||
channel.setChannelTypeCode(meta.channelTypeCode);
|
||||
channel.setChannelTypeVersion(meta.channelTypeVersion);
|
||||
channel.setSubscribersCount(loadSubscribersCount(c, ownerBch, lineCode, meta.channelTypeCode));
|
||||
Net_GetChannelMessages_Response.BlockRef rootRef = new Net_GetChannelMessages_Response.BlockRef();
|
||||
rootRef.setBlockNumber(lineCode);
|
||||
rootRef.setBlockHash(req.getChannel().getChannelRootBlockHash());
|
||||
@@ -180,4 +183,28 @@ public class Net_GetChannelMessages_Handler implements JsonMessageHandler {
|
||||
return NetExceptionResponseFactory.error(req, WireCodes.Status.INTERNAL_ERROR, "internal_error", "Внутренняя ошибка сервера");
|
||||
}
|
||||
}
|
||||
|
||||
private static int loadSubscribersCount(Connection c, String ownerBch, int rootNumber, int channelTypeCode) {
|
||||
if (channelTypeCode != (CreateChannelBody.CHANNEL_TYPE_PUBLIC & 0xFFFF)) {
|
||||
return 0;
|
||||
}
|
||||
String sql = """
|
||||
SELECT subscribers_count
|
||||
FROM channel_stats_state
|
||||
WHERE owner_bch_name = ?
|
||||
AND channel_root_block_number = ?
|
||||
LIMIT 1
|
||||
""";
|
||||
try (PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, ownerBch);
|
||||
ps.setInt(2, rootNumber);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) return 0;
|
||||
return Math.max(0, rs.getInt("subscribers_count"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("GetChannelMessages: не удалось загрузить subscribers_count для {}#{}", ownerBch, rootNumber, e);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
@@ -32,6 +32,7 @@ public class Net_GetChannelMessages_Response extends Net_Response {
|
||||
private Long metaUpdatedAtMs;
|
||||
private Integer channelTypeCode;
|
||||
private Integer channelTypeVersion;
|
||||
private Integer subscribersCount;
|
||||
private BlockRef channelRoot;
|
||||
|
||||
public String getOwnerLogin() { return ownerLogin; }
|
||||
@@ -67,6 +68,9 @@ public class Net_GetChannelMessages_Response extends Net_Response {
|
||||
public Integer getChannelTypeVersion() { return channelTypeVersion; }
|
||||
public void setChannelTypeVersion(Integer channelTypeVersion) { this.channelTypeVersion = channelTypeVersion; }
|
||||
|
||||
public Integer getSubscribersCount() { return subscribersCount; }
|
||||
public void setSubscribersCount(Integer subscribersCount) { this.subscribersCount = subscribersCount; }
|
||||
|
||||
public BlockRef getChannelRoot() { return channelRoot; }
|
||||
public void setChannelRoot(BlockRef channelRoot) { this.channelRoot = channelRoot; }
|
||||
}
|
||||
|
||||
+36
@@ -11,11 +11,15 @@ import server.logic.ws_protocol.JSON.handlers.tempToTest.entyties.Net_GetUser_Re
|
||||
import server.logic.ws_protocol.JSON.utils.NetExceptionResponseFactory;
|
||||
import server.logic.ws_protocol.WireCodes;
|
||||
import shine.db.dao.BlockchainStateDAO;
|
||||
import shine.db.DbController;
|
||||
import shine.db.dao.CurrentUsersDAO;
|
||||
import shine.db.entities.BlockchainStateEntry;
|
||||
import shine.db.entities.CurrentUserEntry;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Net_GetUser_Handler implements JsonMessageHandler {
|
||||
@@ -63,6 +67,7 @@ public class Net_GetUser_Handler implements JsonMessageHandler {
|
||||
resp.setSolanaKey(u.getSolanaKey());
|
||||
resp.setBlockchainKey(u.getBlockchainKey());
|
||||
resp.setClientKey(u.getClientKey());
|
||||
loadUserStats(resp, u.getLogin());
|
||||
|
||||
// Возвращаем актуальный курсор блокчейна и, если запись состояния потеряна,
|
||||
// автоматически восстанавливаем её для существующего пользователя.
|
||||
@@ -125,4 +130,35 @@ public class Net_GetUser_Handler implements JsonMessageHandler {
|
||||
}
|
||||
return new String(out);
|
||||
}
|
||||
|
||||
private static void loadUserStats(Net_GetUser_Response resp, String login) {
|
||||
resp.setOwnedPublicChannelsCount(0);
|
||||
resp.setFollowingUsersCount(0);
|
||||
resp.setFollowingChannelsCount(0);
|
||||
resp.setCloseFriendsCount(0);
|
||||
String sql = """
|
||||
SELECT owned_public_channels_count,
|
||||
following_users_count,
|
||||
following_channels_count,
|
||||
close_friends_count
|
||||
FROM user_stats_state
|
||||
WHERE login = ?
|
||||
LIMIT 1
|
||||
""";
|
||||
try (Connection c = DbController.getInstance().getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
ps.setString(1, login);
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) {
|
||||
return;
|
||||
}
|
||||
resp.setOwnedPublicChannelsCount(rs.getInt("owned_public_channels_count"));
|
||||
resp.setFollowingUsersCount(rs.getInt("following_users_count"));
|
||||
resp.setFollowingChannelsCount(rs.getInt("following_channels_count"));
|
||||
resp.setCloseFriendsCount(rs.getInt("close_friends_count"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("GetUser: не удалось загрузить статистику для login={}", login, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
@@ -43,6 +43,10 @@ public class Net_GetUser_Response extends Net_Response {
|
||||
private String serverLastGlobalHash;
|
||||
private Long serverBlockchainSizeBytes;
|
||||
private Long serverBlockchainSizeLimitBytes;
|
||||
private Integer ownedPublicChannelsCount;
|
||||
private Integer followingUsersCount;
|
||||
private Integer followingChannelsCount;
|
||||
private Integer closeFriendsCount;
|
||||
|
||||
public Boolean getExists() { return exists; }
|
||||
public void setExists(Boolean exists) { this.exists = exists; }
|
||||
@@ -74,4 +78,16 @@ public class Net_GetUser_Response extends Net_Response {
|
||||
public Long getServerBlockchainSizeLimitBytes() { return serverBlockchainSizeLimitBytes; }
|
||||
public void setServerBlockchainSizeLimitBytes(Long serverBlockchainSizeLimitBytes) { this.serverBlockchainSizeLimitBytes = serverBlockchainSizeLimitBytes; }
|
||||
|
||||
public Integer getOwnedPublicChannelsCount() { return ownedPublicChannelsCount; }
|
||||
public void setOwnedPublicChannelsCount(Integer ownedPublicChannelsCount) { this.ownedPublicChannelsCount = ownedPublicChannelsCount; }
|
||||
|
||||
public Integer getFollowingUsersCount() { return followingUsersCount; }
|
||||
public void setFollowingUsersCount(Integer followingUsersCount) { this.followingUsersCount = followingUsersCount; }
|
||||
|
||||
public Integer getFollowingChannelsCount() { return followingChannelsCount; }
|
||||
public void setFollowingChannelsCount(Integer followingChannelsCount) { this.followingChannelsCount = followingChannelsCount; }
|
||||
|
||||
public Integer getCloseFriendsCount() { return closeFriendsCount; }
|
||||
public void setCloseFriendsCount(Integer closeFriendsCount) { this.closeFriendsCount = closeFriendsCount; }
|
||||
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import blockchain.body.ConnectionBody;
|
||||
import blockchain.body.CreateChannelBody;
|
||||
import blockchain.body.HeaderBody;
|
||||
import blockchain.body.TextBody;
|
||||
import shine.db.DbController;
|
||||
import test.it.blockchain.AddBlockSender;
|
||||
import test.it.blockchain.ChainState;
|
||||
import test.it.utils.TestConfig;
|
||||
@@ -13,6 +14,10 @@ import test.it.utils.log.TestResult;
|
||||
import test.it.utils.ws.WsSession;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@@ -117,6 +122,27 @@ public class IT_03_AddBlock_NoAuth {
|
||||
st1.registerTextChannelRoot(newsRootBlock, newsRootHash);
|
||||
}
|
||||
|
||||
// CREATE_CHANNEL "Updates" — второй публичный канал того же владельца
|
||||
int updatesRootBlock;
|
||||
byte[] updatesRootHash;
|
||||
{
|
||||
var ln = st1.nextLineByType(ChainState.TYPE_TECH);
|
||||
sender1.send(new CreateChannelBody(
|
||||
0,
|
||||
ln.prevLineNumber, ln.prevLineHash32, ln.thisLineNumber,
|
||||
"Updates",
|
||||
"",
|
||||
CreateChannelBody.CHANNEL_TYPE_PUBLIC,
|
||||
CreateChannelBody.CHANNEL_TYPE_VERSION_DEFAULT
|
||||
), t);
|
||||
|
||||
updatesRootBlock = st1.lastBlockNumber();
|
||||
updatesRootHash = st1.getHash32(updatesRootBlock);
|
||||
assertNotNull(updatesRootHash);
|
||||
|
||||
st1.registerTextChannelRoot(updatesRootBlock, updatesRootHash);
|
||||
}
|
||||
|
||||
// POST #0 в канал "News"
|
||||
int newsPost0Block;
|
||||
byte[] newsPost0Hash;
|
||||
@@ -188,7 +214,21 @@ public class IT_03_AddBlock_NoAuth {
|
||||
bch1, newsRootBlock, newsRootHash,
|
||||
"U2 follows U1 channel 'News' (target=U1 CREATE_CHANNEL root)", t);
|
||||
|
||||
// 3) FRIEND взаимно (на HEADER)
|
||||
// 3) U2 подписался на второй канал U1 "Updates"
|
||||
sendConnection(sender2, st2, MsgSubType.CONNECTION_FOLLOW,
|
||||
bch1, updatesRootBlock, updatesRootHash,
|
||||
"U2 follows U1 channel 'Updates' (target=U1 CREATE_CHANNEL root)", t);
|
||||
|
||||
assertEquals(2, countConnectionsByOwner(u2, u1),
|
||||
"U2 должен иметь две отдельные записи подписки на каналы U1");
|
||||
assertEquals(2, countFollowingChannels(u2),
|
||||
"following_channels_count должен учитывать два разных канала одного владельца");
|
||||
assertEquals(1, countSubscribers(bch1, newsRootBlock, newsRootHash),
|
||||
"У канала News должен быть 1 подписчик");
|
||||
assertEquals(1, countSubscribers(bch1, updatesRootBlock, updatesRootHash),
|
||||
"У канала Updates должен быть 1 подписчик");
|
||||
|
||||
// 4) FRIEND взаимно (на HEADER)
|
||||
sendConnection(sender1, st1, MsgSubType.CONNECTION_CLOSE_FRIEND,
|
||||
bch2, u2HeaderBlock, u2HeaderHash,
|
||||
"U1 -> U2: FRIEND", t);
|
||||
@@ -197,7 +237,7 @@ public class IT_03_AddBlock_NoAuth {
|
||||
bch1, u1HeaderBlock, u1HeaderHash,
|
||||
"U2 -> U1: FRIEND", t);
|
||||
|
||||
// 4) CONTACT несколько
|
||||
// 5) CONTACT несколько
|
||||
sendConnection(sender1, st1, MsgSubType.CONNECTION_CONTACT,
|
||||
bch2, u2HeaderBlock, u2HeaderHash,
|
||||
"U1 -> U2: CONTACT", t);
|
||||
@@ -236,7 +276,21 @@ public class IT_03_AddBlock_NoAuth {
|
||||
bch3, u3HeaderBlock, u3HeaderHash,
|
||||
"U1 -> U3: CONTACT", t);
|
||||
|
||||
// 5) U1 убирает U2 из контактов (UNCONTACT)
|
||||
// 6) U2 отписывается только от News
|
||||
sendConnection(sender2, st2, MsgSubType.CONNECTION_UNFOLLOW,
|
||||
bch1, newsRootBlock, newsRootHash,
|
||||
"U2 unfollows U1 channel 'News'", t);
|
||||
|
||||
assertEquals(1, countConnectionsByOwner(u2, u1),
|
||||
"После отписки от одного канала должна остаться одна запись подписки");
|
||||
assertEquals(1, countFollowingChannels(u2),
|
||||
"following_channels_count должен уменьшиться ровно на 1");
|
||||
assertEquals(0, countSubscribers(bch1, newsRootBlock, newsRootHash),
|
||||
"После отписки от News у канала должен остаться 0 подписчиков");
|
||||
assertEquals(1, countSubscribers(bch1, updatesRootBlock, updatesRootHash),
|
||||
"Подписка на Updates должна остаться");
|
||||
|
||||
// 7) U1 убирает U2 из контактов (UNCONTACT)
|
||||
sendConnection(sender1, st1, MsgSubType.CONNECTION_UNCONTACT,
|
||||
bch2, u2HeaderBlock, u2HeaderHash,
|
||||
"U1 -> U2: UNCONTACT", t);
|
||||
@@ -288,4 +342,58 @@ public class IT_03_AddBlock_NoAuth {
|
||||
toBlockHash32
|
||||
), timeout);
|
||||
}
|
||||
|
||||
private static int countConnectionsByOwner(String login, String ownerLogin) {
|
||||
return queryInt("""
|
||||
SELECT COUNT(*)
|
||||
FROM connections_state
|
||||
WHERE login = ?
|
||||
AND rel_type = 30
|
||||
AND to_login = ?
|
||||
""", login, ownerLogin);
|
||||
}
|
||||
|
||||
private static int countFollowingChannels(String login) {
|
||||
return queryInt("""
|
||||
SELECT following_channels_count
|
||||
FROM user_stats_state
|
||||
WHERE login = ?
|
||||
""", login);
|
||||
}
|
||||
|
||||
private static int countSubscribers(String ownerBlockchainName, int rootBlockNumber, byte[] rootBlockHash) {
|
||||
return queryInt("""
|
||||
SELECT COALESCE(subscribers_count, 0)
|
||||
FROM channel_stats_state
|
||||
WHERE owner_bch_name = ?
|
||||
AND channel_root_block_number = ?
|
||||
AND channel_root_block_hash = ?
|
||||
""", ownerBlockchainName, rootBlockNumber, rootBlockHash);
|
||||
}
|
||||
|
||||
private static int queryInt(String sql, Object... params) {
|
||||
try (Connection c = DbController.getInstance().getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
Object p = params[i];
|
||||
if (p instanceof String s) {
|
||||
ps.setString(i + 1, s);
|
||||
} else if (p instanceof Integer n) {
|
||||
ps.setInt(i + 1, n);
|
||||
} else if (p instanceof byte[] bytes) {
|
||||
ps.setBytes(i + 1, bytes);
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unsupported SQL param type: " + (p == null ? "null" : p.getClass()));
|
||||
}
|
||||
}
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
if (!rs.next()) {
|
||||
throw new IllegalStateException("Query returned no rows: " + sql);
|
||||
}
|
||||
return rs.getInt(1);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("DB query failed: " + sql, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user