Skip to content

Loading…

Separate location #12

Open
wants to merge 2 commits into from

2 participants

@cbbrowne

Here is a "drafty" form of drawing location off of the main ship table to a separate ship_location table.

@cbbrowne

I would like to drop the location from the ship table, but that makes the trigger and rule definitions problematic to define.

Owner

The new location record would need to be inserted in the my_ships insert rule. Then, a trigger on ship_location would need to validate the chosen location.

@cbbrowne

I expect that we'll eventually want to put all of the motion-related attributes here. Location vector, motion vector, perhaps eventually burn vector.

Fuel, too.

Owner

MOVE_SHIPS() is going to need to update location, speed and direction. So those are the columns I would assume we want to be together. Target_speed, target_direction and destination are all user updatable so they would be best in their own table (ship_control). This should allow MOVE_SHIPS() and SHIP_COURSE_CONTROL() to be run at the same time without deadlocking.

@cbbrowne

I wonder if it is the right thing to "insert to my_ships."

It seems to me that this will require increasing amounts of cleverness as attributes get added.

I'm finding I want to do multiple inserts (e.g. - need INSERT INTO ship(), INSERT INTO ship_location (), possibly more...), which doesn't seem to work very well.

Probably what has to happen is for this to be refactored into some function calls.

Thus, something more like:

CREATE OR REPLACE RULE ship_insert AS ON INSERT TO my_ships DO INSTEAD
perform create_ship (parameters);

Suppose create_ship() returns the new ship ID, then we want...

perform locate_ship(create_ship(parameters), location);

That works if the ID is only used once. If there turn out to be two functions that require the ship ID, then it all has to get wrapped together. Perhaps that simply means that create_ship() does everything, and maybe there's no need for a trigger on ship.

Owner

Hmm, good points about the ship's ID. I am going to have to play around with this and see if we can find a reliable way of tossing the ID around between tables or storing the initial location somewhere in a column like.. initial_location or which planet the ship originated from (this may limit future development though if ships can one day be created at somewhere that isn't a planet).

Well, the value of the ID is in currval('whatever-is-the-sequence'), so it's referenceable, as long as the trigger/rule does so after the ship is inserted.

@Abstrct
Owner

I was a bit worried about currval(seq) due to the way ID's are handled in the game but I just gave some code a go to test it and there were no complications. There is no reason why we cannot trust currval('ship_id_seq) in the insert into the ship_location table during the my_ships view insert rule.

CREATE SEQUENCE s_seq
INCREMENT 1
MINVALUE 1
MAXVALUE 9223372036854775807
START 1
CACHE 1;

create table s (
id integer,
val integer
);
create table s_l (
id integer,
val integer
);
create view s_v as select
s.id, s.val sval, s_l.val as slval FROM s, s_l WHERE s.id=s_l.id;

CREATE OR REPLACE RULE s_insert AS
ON INSERT TO s_v DO INSTEAD (
INSERT INTO s(val) VALUES(NEW.sval);
INSERT INTO s_l VALUES(currval('s_seq'), NEW.slval);
) ;

CREATE OR REPLACE FUNCTION id_dealer()
RETURNS trigger AS
$BODY$
BEGIN
NEW.id = nextval('s_seq');

RETURN NEW;
END
$BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER
COST 100;

CREATE TRIGGER d_dealer
BEFORE INSERT OR UPDATE
ON s
FOR EACH ROW
EXECUTE PROCEDURE id_dealer();

-- Run each of these in a different process
INSERT INTO s_v(slval, sval) SELECT 1,1 from generate_series(1,1000000);
INSERT INTO s_v(slval, sval) SELECT 2,2 from generate_series(1,1000000);
INSERT INTO s_v(slval, sval) SELECT 3,3 from generate_series(1,1000000);

--Check to missmatched results
select * from s_v where slval <> sval
-- No Results were returned

@cbbrowne

There is an alternate perspective of "currval" which involves the function returning the most recently generated sequence value from ANY sequence associated with your DB connection.

Which is perhaps the most terrible behavior possible; it means that if you decided to (say) replicate Schemaverse using Slony or Londiste, you might instead capture sequence values being used as part of the internals of those replication systems. Horrible, horrible, horrible.

(Poking at docs...) Ah, the function that does this is lastval(). http://www.postgresql.org/docs/9.1/static/functions-sequence.html You'd have to be insane and stupid to use lastval() for anything.

But if you use currval('specific_seq_name'), it's all good.

A couple years ago, I was working on an experimental domain registry prototype where we were using currval() extremely heavily to establish transaction contexts. Basically, we'd create a logical transaction:
insert into transaction (id, when, who) values (nextval('tx'), now(), 'my-user-name');

and then, throughout the rest of the DB activity, HEAVY use was made of currval('tx') to reference the transaction context data.

"Oh, what time are we using for this transaction?" (It didn't have to be NOW() - it was meaningful to backdate/forward date activity.)

  • select when from transaction where id = currval('tx');

"Oh, who's the user?"

  • select who from transaction where id = currval('tx');

We'd attach all kinds of additional data to the transaction by joining in extra tables; the key to getting at it was always currval('tx'). And this worked out AOK. I have a fair bit of faith in currval() :-).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 6, 2012
  1. @cbbrowne

    Draw current_health out into separate table, ship_health. This is pro…

    cbbrowne committed
    …bably not complete; future_health should also be drawn out, possibly to the same table.
Commits on Mar 7, 2012
  1. @cbbrowne
Showing with 86 additions and 68 deletions.
  1. +86 −68 create_schemaverse.sql
View
154 create_schemaverse.sql
@@ -1,6 +1,7 @@
-- Schemaverse
-- Created by Josh McDougall
-- v1.1.0 - location, location, location
+
begin;
CREATE SEQUENCE round_seq
@@ -407,7 +408,6 @@ CREATE TABLE ship
name character varying,
last_action_tic integer default '0',
last_living_tic integer default '0',
- current_health integer NOT NULL DEFAULT '100' CHECK (current_health <= max_health),
max_health integer NOT NULL DEFAULT '100',
future_health integer default '100',
current_fuel integer NOT NULL DEFAULT '1100' CHECK (current_fuel <= max_fuel),
@@ -418,10 +418,20 @@ CREATE TABLE ship
defense integer NOT NULL DEFAULT '5',
engineering integer NOT NULL default '5',
prospecting integer NOT NULL default '5',
- location point default point(0,0),
+ location point, -- Don't want this column, but NEED it :-(
destroyed boolean NOT NULL default 'f'
);
+create table ship_health (
+ ship_id integer primary key references ship(id) on delete cascade,
+ current_hurt integer not null
+);
+
+create table ship_location (
+ ship_id integer primary key references ship(id) on delete cascade,
+ location point not null default point(0,0)
+);
+
CREATE OR REPLACE FUNCTION GET_SHIP_NAME(ship_id integer) RETURNS character varying AS $get_ship_name$
DECLARE
found_shipname character varying;
@@ -445,6 +455,7 @@ CREATE TABLE ship_control
action_target_id integer,
PRIMARY KEY (ship_id)
);
+
CREATE SEQUENCE ship_id_seq
INCREMENT 1
MINVALUE 1
@@ -496,20 +507,20 @@ declare
begin
SELECT last_value INTO current_tic FROM tic_seq;
- FOR NEW IN SELECT id, range, location FROM ship
- WHERE last_move_tic=current_tic
+ FOR NEW IN SELECT id, range, sl.location FROM ship, ship_location sl
+ WHERE last_move_tic=current_tic and sl.ship_id = ship.id
LOOP
delete from ships_near_ships where first_ship = NEW.id;
delete from ships_near_ships where second_ship = NEW.id;
insert into ships_near_ships (first_ship, second_ship, location_first, location_second, distance)
- select NEW.id, s2.id, NEW.location, s2.location, NEW.location <-> s2.location
- from ship s2
- where s2.id <> NEW.id and (NEW.location <-> s2.location) < NEW.range;
+ select NEW.id, s2.ship_id, NEW.location, s2.location, NEW.location <-> s2.location
+ from ship_location s2
+ where s2.ship_id <> NEW.id and (NEW.location <-> s2.location) < NEW.range;
insert into ships_near_ships (first_ship, second_ship, location_first, location_second, distance)
- select s1.id, NEW.id, s1.location, NEW.location, NEW.location <-> s1.location
- from ship s1
- where s1.id <> NEW.id and (s1.location <-> NEW.location) < s1.range;
+ select s1.id, NEW.id, sl1.location, NEW.location, NEW.location <-> s1.location
+ from ship s1, ship_location sl1
+ where s1.id <> NEW.id and sl1.ship_id = s1.id and (sl1.location <-> NEW.location) < s1.range;
end LOOP;
return 't';
end
@@ -523,19 +534,9 @@ SELECT
players.id as ship_in_range_of,
enemies.player_id as player_id,
enemies.name as name,
- ((enemies.current_health)::numeric/(enemies.max_health)::numeric)::numeric as health,
- --enemies.current_health as current_health,
- --enemies.max_health as max_health,
- --enemies.current_fuel as current_fuel,
- --enemies.max_fuel as max_fuel,
- --enemies.max_speed as max_speed,
- --enemies.range as range,
- --enemies.attack as attack,
- --enemies.defense as defense,
- --enemies.engineering as engineering,
- --enemies.prospecting as prospecting,
- enemies.location as enemy_location
-FROM ship enemies, ship players, ships_near_ships
+ ((enemies.max_health-coalesce((select current_hurt from ship_health sh where sh.ship_id=enemies.id),0))::numeric/(enemies.max_health)::numeric)::numeric as health,
+ eloc.location as enemy_location
+FROM ship enemies, ship players, ships_near_ships, ship_location eloc, ship_location ploc
WHERE
not (enemies.destroyed) and not (players.destroyed)
AND
@@ -545,7 +546,11 @@ WHERE
AND
enemies.player_id!=GET_PLAYER_ID(SESSION_USER)
AND
- (enemies.location <-> players.location) <= players.range;
+ (eloc.location <-> ploc.location) <= players.range
+ AND
+ eloc.ship_id = enemies.id
+ AND
+ ploc.ship_id = players.id;
CREATE VIEW my_ships AS
SELECT
@@ -555,7 +560,7 @@ SELECT
ship.name as name,
ship.last_action_tic as last_action_tic,
ship.last_living_tic as last_living_tic,
- ship.current_health as current_health,
+ ship.max_health - coalesce((select current_hurt from ship_health sh where sh.ship_id = ship.id),0) as current_health,
ship.max_health as max_health,
ship.current_fuel as current_fuel,
ship.max_fuel as max_fuel,
@@ -565,7 +570,7 @@ SELECT
ship.defense as defense,
ship.engineering as engineering,
ship.prospecting as prospecting,
- ship.location,
+ shiploc.location,
ship_control.direction as direction,
ship_control.speed as speed,
ship_control.destination,
@@ -574,8 +579,8 @@ SELECT
ship_control.repair_priority as repair_priority,
ship_control.action as action,
ship_control.action_target_id as action_target_id
-FROM ship, ship_control
-WHERE player_id=GET_PLAYER_ID(SESSION_USER) and ship.id=ship_control.ship_id and destroyed='f';
+FROM ship, ship_control, ship_location shiploc
+WHERE player_id=GET_PLAYER_ID(SESSION_USER) and ship.id=ship_control.ship_id and destroyed='f' and shiploc.ship_id = ship.id;
CREATE OR REPLACE RULE ship_insert AS ON INSERT TO my_ships
DO INSTEAD INSERT INTO ship (name, range, attack, defense, engineering, prospecting, location, last_living_tic, fleet_id)
@@ -588,7 +593,7 @@ CREATE OR REPLACE RULE ship_insert AS ON INSERT TO my_ships
COALESCE(new.location, point(0,0)),
(( SELECT tic_seq.last_value FROM tic_seq)),
COALESCE(new.fleet_id, NULL::integer))
- RETURNING ship.id, ship.fleet_id, ship.player_id, ship.name, ship.last_action_tic, ship.last_living_tic, ship.current_health, ship.max_health, ship.current_fuel, ship.max_fuel, ship.max_speed,
+ RETURNING ship.id, ship.fleet_id, ship.player_id, ship.name, ship.last_action_tic, ship.last_living_tic, ship.max_health, ship.max_health, ship.current_fuel, ship.max_fuel, ship.max_speed,
ship.range, ship.attack, ship.defense, ship.engineering, ship.prospecting, ship.location, 0, 0, point(0,0), 0, 0, 0,''::CHARACTER(30),0;
@@ -609,7 +614,6 @@ CREATE OR REPLACE RULE ship_control_update AS ON UPDATE TO my_ships
CREATE OR REPLACE FUNCTION CREATE_SHIP() RETURNS trigger AS $create_ship$
BEGIN
--CHECK SHIP STATS
- NEW.current_health = 100;
NEW.max_health = 100;
NEW.current_fuel = 100;
NEW.max_fuel = 100;
@@ -698,16 +702,25 @@ END
$create_ship_controller$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER CREATE_SHIP_CONTROLLER AFTER INSERT ON ship
- FOR EACH ROW EXECUTE PROCEDURE CREATE_SHIP_CONTROLLER();
+ FOR EACH ROW EXECUTE PROCEDURE CREATE_SHIP_CONTROLLER();
+CREATE OR REPLACE FUNCTION CREATE_SHIP_LOCATION() RETURNS trigger AS $$
+BEGIN
+ INSERT INTO ship_location(ship_id, location) VALUES(NEW.id, NEW.location);
+ NEW.location = NULL::point;
+ RETURN NEW;
+END
+$$ LANGUAGE plpgsql SECURITY DEFINER;
+create trigger create_ship_location after insert on ship
+ for each row execute procedure create_ship_location();
CREATE OR REPLACE FUNCTION ship_move_update()
RETURNS trigger AS
$BODY$
BEGIN
IF NOT NEW.location ~= OLD.location THEN
- INSERT INTO ship_flight_recorder(ship_id, tic, location) VALUES(NEW.id, (SELECT last_value FROM tic_seq), NEW.location);
+ INSERT INTO ship_flight_recorder(ship_id, tic, location) VALUES(NEW.ship_id, (SELECT last_value FROM tic_seq), NEW.location);
END IF;
RETURN NULL;
END $BODY$
@@ -715,9 +728,8 @@ END $BODY$
COST 100;
-CREATE TRIGGER SHIP_MOVE_UPDATE AFTER UPDATE ON ship
+CREATE TRIGGER SHIP_MOVE_UPDATE AFTER UPDATE ON ship_location
FOR EACH ROW EXECUTE PROCEDURE SHIP_MOVE_UPDATE();
-
CREATE TABLE fleet
(
@@ -1025,7 +1037,7 @@ BEGIN
DELETE FROM item_location WHERE location ~= found_item.location and system_name=found_item.system_name;
INSERT INTO player_inventory(player_id, item) VALUES(NEW.player_id, found_item.system_name);
INSERT INTO event(action, player_id_1, ship_id_1, location, descriptor_string, public, tic)
- VALUES('FIND_ITEM',NEW.player_id, NEW.id , NEW.location, found_item.system_name, 'f',(SELECT last_value FROM tic_seq));
+ VALUES('FIND_ITEM',(select player_id from ship where id = NEW.ship_id), NEW.ship_id , NEW.location, found_item.system_name, 'f',(SELECT last_value FROM tic_seq));
END LOOP;
RETURN NEW;
END
@@ -1033,7 +1045,7 @@ $BODY$
LANGUAGE plpgsql VOLATILE SECURITY DEFINER
COST 100;
-CREATE TRIGGER DISCOVER_ITEM AFTER UPDATE ON ship
+CREATE TRIGGER DISCOVER_ITEM AFTER UPDATE ON ship_location
FOR EACH ROW EXECUTE PROCEDURE DISCOVER_ITEM();
CREATE TABLE action
@@ -1358,7 +1370,7 @@ SELECT
trade_item.descriptor as descriptor,
ship.id as ship_id,
ship.name as ship_name,
- ship.current_health as ship_current_health,
+ ship.max_health - coalesce((select current_hurt from ship_health sh where sh.ship_id = ship.id),0) as ship_current_health,
ship.max_health as ship_max_health,
ship.current_fuel as ship_current_fuel,
ship.max_fuel as ship_max_fuel,
@@ -1368,8 +1380,8 @@ SELECT
ship.defense as ship_defense,
ship.engineering as ship_engineering,
ship.prospecting as ship_prospecting,
- ship.location as ship_location
-FROM trade, trade_item, ship WHERE
+ sloc.location as ship_location
+FROM trade, trade_item, ship, ship_location sloc WHERE
GET_PLAYER_ID(SESSION_USER) IN (trade.player_id_1, trade.player_id_2)
AND
trade.id=trade_item.trade_id
@@ -1378,7 +1390,8 @@ trade.complete='f'
AND
trade_item.description_code ='SHIP'
AND
-ship.id=CAST(trade_item.descriptor as integer);
+ship.id=CAST(trade_item.descriptor as integer) and
+sloc.ship_id = ship.id;
@@ -1673,15 +1686,15 @@ declare
begin
SELECT last_value INTO current_tic FROM tic_seq;
- FOR NEW IN SELECT id, range, location FROM ship
- WHERE last_move_tic=current_tic
+ FOR NEW IN SELECT id, range, sl.location FROM ship, ship_location sl
+ WHERE last_move_tic=current_tic and sl.ship_id = ship.id
LOOP
delete from ships_near_planets where ship = NEW.id;
-- Record the 10 planets that are nearest to the specified ship
insert into ships_near_planets (ship, planet, ship_location, planet_location, distance)
select NEW.id, p.id, NEW.location, p.location, NEW.location <-> p.location
- from planets p
+ from planets p
--where CIRCLE(NEW.location, NEW.range) ~ p.location
order by NEW.location <-> p.location desc limit 10;
END LOOP;
@@ -1996,7 +2009,9 @@ CREATE OR REPLACE FUNCTION ACTION_PERMISSION_CHECK(ship_id integer) RETURNS bool
DECLARE
ships_player_id integer;
BEGIN
- SELECT player_id into ships_player_id FROM ship WHERE id=ship_id and destroyed='f' and current_health > 0 and last_action_tic != (SELECT last_value FROM tic_seq);
+ SELECT player_id into ships_player_id FROM ship WHERE id=ship_id and destroyed='f' and
+ not exists (select 1 from ship_health sh where sh.ship_id = id and sh.current_hurt = ship.max_health)
+ and last_action_tic != (SELECT last_value FROM tic_seq);
IF ships_player_id = GET_PLAYER_ID(SESSION_USER) OR SESSION_USER = 'schemaverse'
OR CURRENT_USER = 'schemaverse' THEN
RETURN 't';
@@ -2013,7 +2028,7 @@ BEGIN
SELECT
count(enemies.id)
INTO check_count
- FROM ship enemies, ship players
+ FROM ship enemies, ship players, ship_location eloc, ship_location ploc
WHERE
enemies.destroyed='f' AND players.destroyed='f'
AND
@@ -2022,9 +2037,11 @@ BEGIN
AND
enemies.id=ship_2
)
+ and ploc.ship_id = ship_1
+ and eloc.ship_id = ship_2
AND
(
- (enemies.location <-> players.location) < players.range
+ (eloc.location <-> ploc.location) < players.range
);
IF check_count = 1 THEN
RETURN 't';
@@ -2041,17 +2058,19 @@ BEGIN
SELECT
count(planet.id)
INTO check_count
- FROM planet, ship
+ FROM planet, ship, ship_location sloc
WHERE ship.destroyed='f'
AND
(
ship.id=ship_id
AND
planet.id=planet_id
+ and
+ sloc.ship_id = ship_id
)
AND
(
- (planet.location <-> ship.location) < ship.range
+ (planet.location <-> sloc.location) < ship.range
);
IF check_count = 1 THEN
RETURN 't';
@@ -2081,10 +2100,10 @@ BEGIN
defense_efficiency := GET_NUMERIC_VARIABLE('DEFENSE_EFFICIENCY') / 100::numeric;
--FINE, I won't divide by zero
- SELECT attack + 1, player_id, name, location INTO attack_rate, attacker_player_id, attacker_name, loc FROM ship WHERE id=attacker;
+ SELECT attack + 1, player_id, name, sl.location INTO attack_rate, attacker_player_id, attacker_name, loc FROM ship, ship_location sl WHERE id=attacker and sl.ship_id = attacker;
SELECT defense + 1, player_id, name INTO defense_rate, enemy_player_id, enemy_name FROM ship WHERE id=enemy_ship;
- damage = (attack_rate * (defense_efficiency/defense_rate+defense_efficiency))::integer;
+ damage = (attack_rate * (defense_efficiency/defense_rate+defense_efficiency))::integer;
UPDATE ship SET future_health=future_health-damage WHERE id=enemy_ship;
UPDATE ship SET last_action_tic=(SELECT last_value FROM tic_seq) WHERE id=attacker;
@@ -2107,18 +2126,14 @@ DECLARE
repaired_ship_name character varying;
loc point;
BEGIN
-
repair_rate = 0;
-
-
--check range
IF ACTION_PERMISSION_CHECK(repair_ship) AND (IN_RANGE_SHIP(repair_ship, repaired_ship)) THEN
- SELECT engineering, player_id, name, location INTO repair_rate, repair_ship_player_id, repair_ship_name, loc FROM ship WHERE id=repair_ship;
+ SELECT engineering, player_id, name, sl.location INTO repair_rate, repair_ship_player_id, repair_ship_name, loc FROM ship, ship_location sl WHERE id=repair_ship and sl.ship_id = repair_ship;
SELECT name INTO repaired_ship_name FROM ship WHERE id=repaired_ship;
UPDATE ship SET future_health = future_health + repair_rate WHERE id=repaired_ship;
UPDATE ship SET last_action_tic=(SELECT last_value FROM tic_seq) WHERE id=repair_ship;
-
INSERT INTO event(action, player_id_1,ship_id_1, ship_id_2, descriptor_numeric, location, public, tic)
VALUES('REPAIR',repair_ship_player_id, repair_ship, repaired_ship , repair_rate,loc,'t',(SELECT last_value FROM tic_seq));
@@ -2164,12 +2179,13 @@ BEGIN
planet_miners.ship_id as ship_id,
ship.player_id as player_id,
ship.prospecting as prospecting,
- ship.location as location,
+ sl.location as location,
player.fuel_reserve as fuel_reserve
FROM
- planet_miners, ship, player
+ planet_miners, ship, player, ship_location sl
WHERE
planet_miners.ship_id=ship.id
+ and sl.ship_id = ship.id
AND player.id=ship.player_id
ORDER BY planet_miners.planet_id, (ship.prospecting * RANDOM()) LOOP
@@ -2306,8 +2322,9 @@ CREATE OR REPLACE FUNCTION "move_ships"()
RETURNS boolean AS
$MOVE_SHIPS$
DECLARE
- ship_control record;
+ ship_control_ record;
velocity point;
+ new_location point;
new_velocity point;
vector point;
delta_v numeric;
@@ -2367,14 +2384,19 @@ BEGIN
-- Move the ship!
UPDATE ship S SET
last_move_tic = current_tic,
- current_fuel = ship_control_.current_fuel,
- location = ship_control_.location + point(COS(RADIANS(ship_control_.direction)) * ship_control_.speed,
- SIN(RADIANS(ship_control_.direction)) * ship_control_.speed)
+ current_fuel = ship_control_.current_fuel
WHERE S.id = ship_control_.id;
+ new_location := ship_control_.location + point(COS(RADIANS(ship_control_.direction)) * ship_control_.speed,
+ SIN(RADIANS(ship_control_.direction)) * ship_control_.speed);
+
+ UPDATE ship_location S SET
+ location = new_location
+ where S.ship_id = ship_control_.id;
+
UPDATE ship S SET
- location_x = location[0],
- location_y = location[1]
+ location_x = new_location[0],
+ location_y = new_location[1]
WHERE S.id = ship_control_.id;
UPDATE ship_control SC SET
@@ -2382,9 +2404,6 @@ BEGIN
direction = ship_control_.direction
WHERE SC.ship_id = ship_control_.id;
END LOOP;
-
-
-
RETURN 't';
END
$MOVE_SHIPS$
@@ -2881,11 +2900,10 @@ $round_control$
-- These seem to make the largest improvement for performance
CREATE INDEX event_toc_index ON event USING btree (toc);
CREATE INDEX event_action_index ON event USING hash (action);
-CREATE INDEX ship_location_index ON ship USING GIST (location);
+CREATE INDEX ship_location_index ON ship_location USING GIST (location);
CREATE INDEX planet_location_index ON planet USING GIST (location);
CREATE INDEX ship_player ON ship USING btree (player_id);
-CREATE INDEX ship_health ON ship USING btree (current_health);
CREATE INDEX ship_fleet ON ship USING btree (fleet_id);
CREATE INDEX fleet_player ON fleet USING btree (player_id);
Something went wrong with that request. Please try again.