Mein Datenmodell möge (von Django et.al. inspiriert) so aussehen:
Code: Alles auswählen
class Game(Base):
turn = Int()
players = List('Player')
regions = List('Region')
class Player(Base):
name = Str()
game = Ref('Game.players')
allies = List('Player')
owned_regions = List('Region')
explored_regions = List('Region')
class Region(Base):
name = Str()
game = Ref('Game.regions')
owner = Ref('Player.owned_regions')
Damit kann ich jetzt sowas machen:
Code: Alles auswählen
game = Game(turn=1)
game.players.extend((Player('sma'), Player('fto')))
game.regions = (Region(name) for name in 'Anaheim', 'Boslen', 'Ceshire')
game.regions.get(name='Anaheim').owner = game.players.get(name='sma')
print list(game.players.find(lambda p: len(p.allies) > 2))
Code: Alles auswählen
metadata = sa.MetaData()
games_table = sa.Table('games', metadata,
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('turn', sa.Integer, required=True),
)
players_table = sa.Table('players', metadata,
sa.Column('name', sa.String(30), primary_key=True),
sa.Column('game_id', sa.Integer, ForeignKey('games.id'), required=True),
)
allies_table = sa.Table('allies', metadata,
sa.Column('player1_name', sa.String(30), ForeignKey('players.name'))
sa.Column('player2_name', sa.String(30), ForeignKey('players.name'))
)
regions_table = sa.Table('regions', metadata,
sa.Column('name', sa.String(30), primary_key=True),
sa.Column('owner_name', sa.String(30), ForeignKey('players.name')),
sa.Column('game_id', sa.Integer, ForeignKey('games.id')),
)
explorations_table = sa.Table('explorations', metadata,
sa.Column('explorer_name', sa.String(30), ForeignKey('players.name')),
sa.Column('region_name', sa.String(30), ForeignKey('regions.name')),
)
engine = sa.create_engine("sqlite:...")
metadata.create_all(engine)
class Game(object):
def __init__(self, turn):
self.turn = turn
class Player(object):
def __init__(self, name, game):
self.name, self.game = name, game
class Region(object):
def __init__(self, name, game):
self.name, self.game = name, game
sa.mapper(Game, games_table, properties={
'players': sa.relation(Player, backref='game'),
'regions': sa.relation(Region, backref='game'),
})
sa.mapper(Player, players_table, properties={
'owned_regions': sa.relation(Region, backref='owner'),
'explored_regions': sa.relation(Region, secondary=explorations_table),
'allies': sa.relation(Player, secondary=allies_table),
})
sa.mapper(Region, regions_table)
Wenn ich etwa alle Spieler mit mehr als zwei Verbünderten finden will, reicht dann sowas? Oder wie geht es sonst?
Code: Alles auswählen
session.query(Player).filter(len(Player.allies) > 2)
Code: Alles auswählen
class Game(object):
__storm_table__ = 'game'
id = Int(primary=True)
turn = Int()
class Player(object):
__storm_table__ = 'player'
name = Unicode(primary=True)
game_id = Int()
game = Reference(game_id, Game.id)
class Alliance(object):
__storm_table__ = 'alliance'
__storm_primary__ = 'player1_name', 'player2_name'
player1_name = Unicode()
player2_name = Unicode()
Game.players = ReferenceSet(Game.id, Player.game_id)
Player.allies = ReferenceSet(
Player.name, Alliance.player1.name, Alliance.player2_name, Player.name
)
for p in store.find(Player, len(Player.allies) > 2): print p.name
Bei SQLObject habe ich eine `createTable`-Methode, was ich sehr schön finde. Das Datenmodell sähe wohl so aus:
Code: Alles auswählen
sqlhub.connection = connectionForURI("sqlite:...")
class Game(SQLObject):
turn = IntCol()
class Player(SQLObject):
name = StringCol()
game = ForeignKey('Game')
allies = RelatedJoin('Player')
Game.sqlmeta.addJoin(MultipleJoin('Player'), joinMethodName='players')
Game.createTable()
Player.createTable()
Übrigens, Storm kann offenbar SQLObject emulieren. Da bekommt man dann zwei ORMs für den Preis von einem. Wenn das mal kein gutes Angebot ist ;)
Bei Zodb muss ich gar nichts im Vorfeld definieren. Dafür habe ich Schwierigkeiten, ein gutes Tutorial zu finden. Möglicherweise geht es so:
Code: Alles auswählen
class Game(Persistent):
def __init__(self, turn):
self.turn = turn
self.players = []
def add_player(self, player):
self.players.append(player)
self._p_changed = True
player.game = self
def remove_player(self, player):
self.players.remove(player)
self._p_changed = True
player.game = None
def find_player(self, name):
for player in self.players:
if player.name == name: return player
class Player(Persistent):
def __init__(self, name):
self.name = name
self.allies = set()
def add_ally(self, player):
self.allies.add(player); self._p_changed = True
player.allies.add(self); player._p_changed = True
def remove_ally(self, player):
self.allies.remove(player); self._p_changed = True
player.allies.remove(self); player._p_changed = True
game = Game(1)
game.add_player(Player('sma'))
game.add_player(Player('fto'))
game.find_player('sma').add_ally(game.find_player('fto'))
db = DB(FileStorage.FileStorage('/tmp/...'))
conn = db.open()
conn.root()['game'] = game
transaction.commit()
Es gibt wohl noch "Durus" als Alternative, doch auch dieses Stück Software ist grenzwertig undokumentiert und fühlt sich ungeliebt an. Auch ist deren Transaktionskonzept recht grob (bei Fehlern muss man selbst nochmal von vorn beginnen - erinnert an die GAE).
Ich bilde mir ja ein, dass ich meinen Ansatz zu einer OODB ausbauen könnte (daher hatte ich mir auch Durus angeschaut), tue mich allerdings mit der richtigen Semantik für Transaktionen schwer. Daher wollte ich eigentlich etwas Fertiges haben.
Der Konflikt liegt ja zwischen dem ganzen Objektmodell im Speicher halten (was schnelle Zugriffe erlaubt) und jedes Mal den aktuellen Stand von der Datenbank holen (was aktuelle Daten bedeutet und Fehler oder zumindest Transaktionskonflikte verhindern hilft).
Wer sagt mir etwa, dass ein Spieler, den ich zum Verbündeten machen will, nicht in genau diesem Moment gelöscht wird? Wie stelle ich sicher, dass das (als Beispiel genommen, auch wenn's sicherlich unrealistisch ist) nicht passiert. Muss ich jeweils explizit locken? Unterstützen die erwähnten ORMs das (in den Tutorials jedenfalls nicht)? Sind die ORMs überhaupt threadsafe (Storm ist es nur eingeschränkt)?
Stefan