MySQL Schemata

Plugin works with data containing in following tables. The structure of tables was inspired by PermissionsEx and some other plugins.

Main database tables (v1)

CREATE TABLE IF NOT EXISTS `{DATABASE}`.`{PREFIX}entities` (
   `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
   `entity` VARCHAR(64) NOT NULL,
   `entity_type` TINYINT(1) NOT NULL,
   `prefix` VARCHAR(48) DEFAULT NULL,
   `suffix` VARCHAR(48) DEFAULT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `id_UNIQUE` (`id`),
   UNIQUE KEY `entity_UNIQUE` (`entity`, `entity_type`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET='utf8' COLLATE 'utf8_general_ci';

CREATE TABLE IF NOT EXISTS `{DATABASE}`.`{PREFIX}permissions` (
   `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
   `entity` VARCHAR(64) NOT NULL,
   `entity_type` TINYINT(1) NOT NULL,
   `permission` VARCHAR(255) NOT NULL,
   `value` BIT(1) NOT NULL DEFAULT b'1',
   `destination` VARCHAR(255) NOT NULL DEFAULT '',
   `expirience` SMALLINT(6) NOT NULL DEFAULT 0,
   `lifetime` TIMESTAMP NULL DEFAULT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `id_UNIQUE` (`id`),
   UNIQUE KEY `permission_UNIQUE` (`entity`, `entity_type`, `permission`, `destination`, `expirience`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET='utf8' COLLATE 'utf8_general_ci';

CREATE TABLE IF NOT EXISTS `{DATABASE}`.`{PREFIX}inheritance` (
   `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
   `entity` VARCHAR(64) NOT NULL,
   `parent` VARCHAR(64) NOT NULL,
   `inheritance_type` TINYINT(1) NOT NULL,
   `inheritance_priority` SMALLINT(6) NOT NULL DEFAULT '20',
   `destination` VARCHAR(255) NOT NULL DEFAULT '',
   `expirience` SMALLINT(6) NOT NULL DEFAULT 0,
   `lifetime` TIMESTAMP NULL DEFAULT NULL,
   PRIMARY KEY (`id`, `entity`),
   UNIQUE KEY `id_UNIQUE` (`id`),
   UNIQUE KEY `inheritance_UNIQUE` (`entity`, `parent`, `inheritance_type`, `destination`, `expirience`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET='utf8' COLLATE 'utf8_general_ci';

CREATE TABLE IF NOT EXISTS `{DATABASE}`.`{PREFIX}ladders` (
   `id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
   `climber` VARCHAR(64) NOT NULL,
   `climber_type` TINYINT(1) NOT NULL DEFAULT b'0',
   `ladder` VARCHAR(32) NOT NULL,
   `rank` SMALLINT(6) NOT NULL,
   PRIMARY KEY (`id`),
   UNIQUE KEY `id_UNIQUE` (`id`),
   UNIQUE KEY `climber_UNIQUE` (`climber`, `climber_type`, `ladder`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET='utf8' COLLATE 'utf8_general_ci';

CREATE TABLE IF NOT EXISTS `{DATABASE}`.`{PREFIX}servers` (
   `serverid` VARCHAR(48) NOT NULL,
   `plugin_version` VARCHAR(16) DEFAULT NULL,
   `settings_default-group` VARCHAR(64) NOT NULL DEFAULT 'Default',
   `settings_treat-asterisk-as-op` BIT(1) NOT NULL DEFAULT b'1',
   `settings_auto-reload-delay-sec` INT(10) UNSIGNED NOT NULL DEFAULT '900',
   `settings_maintenance-mode` VARCHAR(64) DEFAULT NULL,
   `settings_enable-rewards` BIT(1) NOT NULL DEFAULT b'0',
   `internal_version` TINYINT(2) UNSIGNED NOT NULL DEFAULT '1',
   PRIMARY KEY (`serverid`),
   UNIQUE KEY `serverid_UNIQUE` (`serverid`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET='utf8' COLLATE 'utf8_general_ci';

Column id is used only as primary key and never as foreigh key. In general, all tables has no any foreigh keys for simplicity.

destination, expirience and lifetime columns together are called conditions.

Plugin automatically drops away all rows with expired lifetime.

All condition columns for group inheritance is applicable just for a concrete player. When resolving permissions for player, inheritance tree is sorted first by priority, then alphabetically.

entity_type, inheritance_type and climber_type equal to 0 if entity (climber) is a group, to 1 if it is a user. parent is always group's name.

Destinations explained

Since serverId, world and region are imploded together into single dabatase column value it should be explained how the values can be entered. Each destination consists of three optional parameters: [region:][world|*][@serverId]. This table describes possible variants of filling it:

valuemeaningexample
empty string or *Everywere*
worldOn any server in worlds with specific nameworld_nether
On any server in all world ending with _the_end*_the_end
world@serverIdIn specific world on specific serverworld_nether@roleplay
*@serverId or @serverIdIn any world on specific server*@creative
region:worldIn named regions in specific worldscity:world
In regions started with plot_ in world worldplot_*:world
region:* or region:Just in this region in any server/worldportals:*
region:*@serverId or region:@serverIdSpecific region names in specific server, any worldpvp_area:*@battle
region:world@serverIdObviously in this region in this world and on this servermyregion:myworld@myserver

Remember, that when resolving permissions and groups for players all entity names and destination subitems are case-independent.

Since beta version v0.5.8b multiple values can be entered in single row. They should be separated by comma (,), semicolon (;) or line breaks (\r or \n). One such row will produce several similar rows in plugin's internals but with different destinations.

Since beta version v0.8.5b parsing rules has changed. At the first, the destination is parsed by regular expression "(?:((?:\w|\*|\?)*):)?((?:\w|\*|\?)*)?(?:@((?:\w|\*|\?)*))?" into three groups (regionId, world, serverId). As you can see each group is optional and can consist of laterals and symbols ? and *. During permission tree resolution question mark means placeholder for ineritance group instantiator. If a player isn't inheriting any group with instantiator, question mark will be present as a text and possibly block current database row! Asterisk is a wildcard that allows zero or more length symbol sequence in destination part.

It should be noted that due to optimizations destination's part serverId is tested only on fetching table into local cache so only wildcard processing is possible for it (no ? processing).

How the permissions are calculated?

If entity's name is not entered into entity column of permissions table, than this permission is implicit (player or group based on entity_type column). Implicit group permissions (IGP) will be applied to every group together with it's own permissions. Implicit player permissions (IPP) are applied to player after all inheritance nodes and mixed with his own permissions.

I have draw a figure to describe the permission tree building process. Permission's tree Is this example permissions will be applied for the player in following order: IGP, Z, IGP, K, IGP, A, IGP, K, IGP, L, IGP, B, IPP, and in the end — own player's permissions.

IGP and IPP can be considered as super-global default permissions is difference to settings.default-group, which is server dependent.

Group prototyping and abstract permissions explained

You can assign permissions containing symbol ? to any groups (or players but this don't have reason!). Let's call them abstract permissions. If player or other group inherits from group containing abstract permissions in the way Parent.Something then abstract permissions become instantiated — symbol ? is changed to Something.

In the inheritance tree instantiation applied by the nearest instantiator. Example:

group A has permission a.?
group B has permission b.?
group B inherits A.bb
user C inherits B.cc

After permission resolution user will have permissions a.bb and b.cc.

Abstract regionId and world name

Since v0.6.2b you can use symbol ? in the place of regionId or world and it will be replaced during recalculation by group's instance (the text after dot in column parent in the table inheritance).

I think this will helps you to create amazing and simple rules for your servers. Good luck.

Optional table for rewards (v1) — draft

Table for rewards will not appear in your database until you manually set settings.enable-rewards to true in config.yml.

KEEP IN MIND THAT THIS FEATURE NOT COMPLETELY REALIZED AND CAN BE CHANGED GREATLY BEFORE RELEASE

CREATE TABLE IF NOT EXISTS `{DATABASE}`.`{PREFIX}rewards` (
	`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
	`user` varchar(64) NOT NULL,
	`code` varchar(16) NOT NULL,
	`activated` bit(1) NOT NULL DEFAULT b'0',
	`activated_timestamp` timestamp NULL DEFAULT NULL,
	`execute_commands` varchar(1024) DEFAULT NULL,
	`command_permissions` varchar(1024) DEFAULT NULL,
	`add_group` varchar(64) DEFAULT NULL,
	`add_group_destination` varchar(256) DEFAULT NULL,
	`add_group_expirience` smallint(6) DEFAULT NULL,
	`add_group_interval` varchar(64) DEFAULT NULL,
	PRIMARY KEY (`id`),
	UNIQUE KEY `id_UNIQUE` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET='utf8' COLLATE 'utf8_general_ci';

Rewards allow you give players ability to make some one-time actions: execute commands from a face of console or player (with temporary given permissions) and to (un-)conditionally inherit player from some group. Columns execute_commands and command_permissions are multiline, lines are separated by \r and/or \n sequinces. Each line in command_permissions means permission node set to true only for time of execution of commands from command_permissions. Each line in command_permissions is a command to execute (without slash) and can be prefixed by "console:" to execute command from console's face or "player:". If not specified commands are executed from the player's face by default.

Columns add_group_* will be explained later.