<commit_before>###
Schema for synchronized editing of strings.

(c) <NAME>, 2016
###

misc = require('./misc')

schema = require('./schema').SCHEMA

# TODO -- currently no security/auth
schema.syncstrings =
    primary_key : 'string_id'
    fields :
        string_id :
            type : 'sha1'
            desc : 'id of this synchronized string sha1 hash of (project_id and path)'
        project_id :
            type : 'uuid'
            desc : 'optional project that this synchronized string belongs to (if it belongs to a project)'
        last_active :
            type : 'timestamp'
            desc : 'when a user most-recently "cared" about this syncstring (syncstring will be automatically opened in running project if last_active is sufficiently recent)'
        last_file_change:
            type : 'timestamp'
            desc : 'when file on disk last changed not due to save (used by Jupyter sync)'
        path :
            type : 'string'
            desc : 'optional path of file being edited'
        init :
            type : 'object'
            desc : '{time:timestamp, error:?} - info about what happened when backend tried to initialize this string'
        save :
            type : 'map'
            desc : "{state:['requested', 'done'], hash:misc.hash_string(what was last saved), error:['' or 'error message']}"
        read_only :
            type : 'boolean'
            desc : 'true or false, depending on whether this syncstring is readonly or can be edited'
        users :
            type : 'array'
            desc : "array of account_id's of those who have edited this string. Index of account_id in this array is used to represent patch authors."
        last_snapshot :
            type : 'timestamp'
            desc : 'timestamp of a recent snapshot; if not given, assume no snapshots.  This is used to restrict the range of patches that have to be downloaded in order start editing the file.'
        snapshot_interval :
            type : 'integer'
            desc : 'If m=snapshot_interval is given and there are a total of n patches, then we (some user) should make snapshots at patches m, 2*m, ..., k, where k<=n-m.'

    indexes:
        project_last_active : ["[that.r.row('project_id'),that.r.row('last_active')]"]

    user_query:
        get :
            all:
                cmd   : 'getAll'
                args  : (obj, db) -> [obj.string_id]
            fields :
                string_id         : (obj, db) -> db.sha1(obj.project_id, obj.path)
                users             : null
                last_snapshot     : null
                snapshot_interval : 150      # unclear how good of a choice 150 is...
                project_id        : null
                path              : null
                save              : null
                last_active       : null
                init              : null
                read_only         : null
                last_file_change  : null
            required_fields :
                path              : true
                project_id        : true
            check_hook : (db, obj, account_id, project_id, cb) ->
                db._syncstrings_check(obj, account_id, project_id, cb)
        set :
            fields :
                string_id         : (obj, db) -> db.sha1(obj.project_id, obj.path)
                users             : true
                last_snapshot     : true
                snapshot_interval : true
                project_id        : true
                path              : true
                save              : true
                last_active       : true
                init              : true
                read_only         : true
                last_file_change  : true
            required_fields :
                path              : true
                project_id        : true
            check_hook : (db, obj, account_id, project_id, cb) ->
                db._syncstrings_check(obj, account_id, project_id, cb)
            on_change : (db, old_val, new_val, account_id, cb) ->
                db._user_set_query_syncstring_change_after(old_val, new_val, account_id, cb)


schema.syncstrings.project_query = misc.deep_copy(schema.syncstrings.user_query)     #TODO -- will be different!

# TODO -- currently no security/auth
schema.recent_syncstrings_in_project =
    primary_key : schema.syncstrings.primary_key
    virtual     : 'syncstrings'
    fields :
        string_id   : true
        project_id  : true
        last_active : true
        path        : true
    user_query :
        get :
            all :
                cmd  : 'between'
                args : (obj, db) -> [[obj.project_id, misc.minutes_ago(obj.max_age_m)], [obj.project_id, db.r.maxval], index:'project_last_active']
            fields :
                project_id  : null
                max_age_m   : 'null'
                string_id   : null
                last_active : null
                path        : null
            required_fields :
                project_id  : true
                max_age_m   : true
            check_hook : (db, obj, account_id, project_id, cb) ->
                db._syncstrings_check(obj, account_id, project_id, cb)

schema.recent_syncstrings_in_project.project_query = schema.recent_syncstrings_in_project.user_query

schema.patches =
    primary_key: 'id'  # this is a compound primary key as an array -- [string_id, time]
    fields:
        id       :
            type : 'compound key [string_id, time]'
            desc : 'Primary key'
        user     :
            type : 'number'
            desc : 'a nonnegative integer; this is an index into syncstrings.users'
        patch    :
            type : 'string'
            desc : 'JSON string the parses to a patch, which goes from the previous of the syncstring to this version'
        snapshot :
            type : 'string'
            desc : 'Optionally, gives the state of the string at this point in time; this should only be set some time after the patch at this point in time was made. Knowing this snap and all future patches determines all the future versions of the syncstring.'
        sent :
            type : 'timestamp'
            desc : 'Optional approximate time at which patch was **actually** sent to the server, which is approximately when it was really made available to other users.  In case of offline editing, patches from days ago might get inserted into the stream, and this makes it possible for the client to know and behave accordingly.  If this is not set then patch was sent about the same time it was created.'
        prev :
            type : 'timestamp'
            desc : "Optional field to indicate patch dependence; if given, don't apply this patch until the patch with timestamp prev has been applied."
    user_query:
        get :
            all :
                cmd : 'between'
                args  : (obj, db) -> [[obj.id[0], obj.id[1] ? db.r.minval], [obj.id[0], db.r.maxval]]
            fields :
                id       : 'null'   # 'null' = field gets used for args above then set to null
                patch    : null
                user     : null
                snapshot : null
                sent     : null
                prev     : null
            check_hook : (db, obj, account_id, project_id, cb) ->
                # this verifies that user has read access to these patches
                db._user_get_query_patches_check(obj, account_id, project_id, cb)
        set :
            fields :
                id       : true
                patch    : true
                user     : true
                snapshot : true
                sent     : true
                prev     : true
            required_fields :
                id       : true
                patch    : true
            check_hook : (db, obj, account_id, project_id, cb) ->
                # this verifies that user has write access to these patches
                db._user_set_query_patches_check(obj, account_id, project_id, cb)

schema.patches.project_query = schema.patches.user_query

schema.cursors =
    primary_key: 'id'  # this is a compound primary key as an array -- [string_id, user_id]
    durability : 'soft' # loss of data for the cursors table just doesn't matter
    fields:
        id   : true    # [string_id, user_id]
        locs : true    # [{x:?,y:?}, ...]    <-- locations of user_id's cursor(s)
        time : true    # time when these cursor positions were sent out
    indexes :
        string_id : ["that.r.row('id')(0)"]
    user_query:
        get :
            all :  # query gets all cursors of *all users* with given string_id -- uses index instead of commented out range query
                #cmd  : 'between'
                #args : (obj, db) -> [[obj.string_id, db.r.minval], [obj.string_id, db.r.maxval]]
                cmd  : 'getAll'
                args : (obj, db) -> [obj.string_id, index:'string_id']
            fields :
                id        : null
                locs      : null
                time      : null
                string_id : 'null'  # virtual -- only used for query, not kept in table<commit_msg>add some constraints on modifying patches<commit_after>###
Schema for synchronized editing of strings.

(c) <NAME>, 2016
###

misc = require('./misc')

schema = require('./schema').SCHEMA

# TODO -- currently no security/auth
schema.syncstrings =
    primary_key : 'string_id'
    fields :
        string_id :
            type : 'sha1'
            desc : 'id of this synchronized string sha1 hash of (project_id and path)'
        project_id :
            type : 'uuid'
            desc : 'optional project that this synchronized string belongs to (if it belongs to a project)'
        last_active :
            type : 'timestamp'
            desc : 'when a user most-recently "cared" about this syncstring (syncstring will be automatically opened in running project if last_active is sufficiently recent)'
        last_file_change:
            type : 'timestamp'
            desc : 'when file on disk last changed not due to save (used by Jupyter sync)'
        path :
            type : 'string'
            desc : 'optional path of file being edited'
        init :
            type : 'object'
            desc : '{time:timestamp, error:?} - info about what happened when backend tried to initialize this string'
        save :
            type : 'map'
            desc : "{state:['requested', 'done'], hash:misc.hash_string(what was last saved), error:['' or 'error message']}"
        read_only :
            type : 'boolean'
            desc : 'true or false, depending on whether this syncstring is readonly or can be edited'
        users :
            type : 'array'
            desc : "array of account_id's of those who have edited this string. Index of account_id in this array is used to represent patch authors."
        last_snapshot :
            type : 'timestamp'
            desc : 'timestamp of a recent snapshot; if not given, assume no snapshots.  This is used to restrict the range of patches that have to be downloaded in order start editing the file.'
        snapshot_interval :
            type : 'integer'
            desc : 'If m=snapshot_interval is given and there are a total of n patches, then we (some user) should make snapshots at patches m, 2*m, ..., k, where k<=n-m.'

    indexes:
        project_last_active : ["[that.r.row('project_id'),that.r.row('last_active')]"]

    user_query:
        get :
            all:
                cmd   : 'getAll'
                args  : (obj, db) -> [obj.string_id]
            fields :
                string_id         : (obj, db) -> db.sha1(obj.project_id, obj.path)
                users             : null
                last_snapshot     : null
                snapshot_interval : 150      # unclear how good of a choice 150 is...
                project_id        : null
                path              : null
                save              : null
                last_active       : null
                init              : null
                read_only         : null
                last_file_change  : null
            required_fields :
                path              : true
                project_id        : true
            check_hook : (db, obj, account_id, project_id, cb) ->
                db._syncstrings_check(obj, account_id, project_id, cb)
        set :
            fields :
                string_id         : (obj, db) -> db.sha1(obj.project_id, obj.path)
                users             : true
                last_snapshot     : true
                snapshot_interval : true
                project_id        : true
                path              : true
                save              : true
                last_active       : true
                init              : true
                read_only         : true
                last_file_change  : true
            required_fields :
                path              : true
                project_id        : true
            check_hook : (db, obj, account_id, project_id, cb) ->
                db._syncstrings_check(obj, account_id, project_id, cb)
            on_change : (db, old_val, new_val, account_id, cb) ->
                db._user_set_query_syncstring_change_after(old_val, new_val, account_id, cb)


schema.syncstrings.project_query = misc.deep_copy(schema.syncstrings.user_query)     #TODO -- will be different!

# TODO -- currently no security/auth
schema.recent_syncstrings_in_project =
    primary_key : schema.syncstrings.primary_key
    virtual     : 'syncstrings'
    fields :
        string_id   : true
        project_id  : true
        last_active : true
        path        : true
    user_query :
        get :
            all :
                cmd  : 'between'
                args : (obj, db) -> [[obj.project_id, misc.minutes_ago(obj.max_age_m)], [obj.project_id, db.r.maxval], index:'project_last_active']
            fields :
                project_id  : null
                max_age_m   : 'null'
                string_id   : null
                last_active : null
                path        : null
            required_fields :
                project_id  : true
                max_age_m   : true
            check_hook : (db, obj, account_id, project_id, cb) ->
                db._syncstrings_check(obj, account_id, project_id, cb)

schema.recent_syncstrings_in_project.project_query = schema.recent_syncstrings_in_project.user_query

schema.patches =
    primary_key: 'id'  # this is a compound primary key as an array -- [string_id, time]
    fields:
        id       :
            type : 'compound key [string_id, time]'
            desc : 'Primary key'
        user     :
            type : 'number'
            desc : 'a nonnegative integer; this is an index into syncstrings.users'
        patch    :
            type : 'string'
            desc : 'JSON string the parses to a patch, which goes from the previous of the syncstring to this version'
        snapshot :
            type : 'string'
            desc : 'Optionally, gives the state of the string at this point in time; this should only be set some time after the patch at this point in time was made. Knowing this snap and all future patches determines all the future versions of the syncstring.'
        sent :
            type : 'timestamp'
            desc : 'Optional approximate time at which patch was **actually** sent to the server, which is approximately when it was really made available to other users.  In case of offline editing, patches from days ago might get inserted into the stream, and this makes it possible for the client to know and behave accordingly.  If this is not set then patch was sent about the same time it was created.'
        prev :
            type : 'timestamp'
            desc : "Optional field to indicate patch dependence; if given, don't apply this patch until the patch with timestamp prev has been applied."
    user_query:
        get :
            all :
                cmd : 'between'
                args  : (obj, db) -> [[obj.id[0], obj.id[1] ? db.r.minval], [obj.id[0], db.r.maxval]]
            fields :
                id       : 'null'   # 'null' = field gets used for args above then set to null
                patch    : null
                user     : null
                snapshot : null
                sent     : null
                prev     : null
            check_hook : (db, obj, account_id, project_id, cb) ->
                # this verifies that user has read access to these patches
                db._user_get_query_patches_check(obj, account_id, project_id, cb)
        set :
            fields :
                id       : true
                patch    : true
                user     : true
                snapshot : true
                sent     : true
                prev     : true
            required_fields :
                id       : true
                patch    : true
            check_hook : (db, obj, account_id, project_id, cb) ->
                # this verifies that user has write access to these patches
                db._user_set_query_patches_check(obj, account_id, project_id, cb)
            before_change : (database, old_val, new_val, account_id, cb) ->
                if old_val?
                    if old_val.sent? and new_val.sent? and new_val.sent != old_val.sent
                        cb("you may not change the sent time once it is set")
                        return
                    if old_val.user? and new_val.user? and old_val.user != new_val.user
                        cb("you may not change the author of a patch from #{old_val.user} to #{new_val.user}")
                        return
                    if old_val.patch? and new_val.patch? and old_val.patch != new_val.patch
                        cb("you may not change a patch")
                        return
                else
                    cb()

schema.patches.project_query = schema.patches.user_query

schema.cursors =
    primary_key: 'id'  # this is a compound primary key as an array -- [string_id, user_id]
    durability : 'soft' # loss of data for the cursors table just doesn't matter
    fields:
        id   : true    # [string_id, user_id]
        locs : true    # [{x:?,y:?}, ...]    <-- locations of user_id's cursor(s)
        time : true    # time when these cursor positions were sent out
    indexes :
        string_id : ["that.r.row('id')(0)"]
    user_query:
        get :
            all :  # query gets all cursors of *all users* with given string_id -- uses index instead of commented out range query
                #cmd  : 'between'
                #args : (obj, db) -> [[obj.string_id, db.r.minval], [obj.string_id, db.r.maxval]]
                cmd  : 'getAll'
                args : (obj, db) -> [obj.string_id, index:'string_id']
            fields :
                id        : null
                locs      : null
                time      : null
                string_id : 'null'  # virtual -- only used for query, not kept in table
{'fraction_non_alphanumeric': 0.07702384071260152, 'fraction_numerical': 0.0019910924810060257, 'mean_word_length': 1.5252712357766605, 'pattern_counts': {'":': 0, '<': 9, '<?xml version=': 0, '>': 32, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4213032', 'n_tokens_mistral': 5102, 'n_tokens_neox': 4893, 'n_words': 1736}