The purpose of this design document is to present a case for changes to be made to the pluggable-db branch in the SVN repository.
· To allow different DB back ends to be plugged into the existing FS while maximizing re-use of existing code. Specifically, in the short term, to allow SQL based DBs to be used in this capacity.
· To allow new variations of the existing baseline FS to live along side it as distinct FS implementations. Allow alternate method versions, which compensate for DB specific strengths or shortcomings, to ensure re-use without introducing brittle code fragments.
· To allow completely re-written FS implementations to live along side the baseline implementation. Large online service organizations may wish to develop their own custom backends with special features for their customers. I encourage them to share with the community of course. J
· To provide an environment where alternate versions of the FS can be developed and shared with the community without compromising the stability of “sanctioned” implementation(s).
I’ll put in a little binding here once I get all the sections re-worked out.
My first attempt to document a pluggable backend contained a figure, which outlined the basic layers involved in the Subversion FS. If the layers touch they talk. I felt it needed to be more informative. So I attached a valve stem and pumped it up to yield figure 1 below.

This more or less reflects the current trunk. The lines represent package-protected (name space protected) calls between the files. If you are unfamiliar with the FS code, or rusty, click the boxes to get a look at the header files. If all else fails, you can always go read the code. But be careful, I’ve heard lore of people going into the code never to returnJ.
The BDB layer is the result of work done by CMike Pilato in an effort to begin separating out BDB dependencies from the FS layer. Its main focus is to separate BDB and “skel” calls from the core FS logic. In case you’re unfamiliar with skels, generally speaking, they are how Subversion facilitates multiple and variable numbers of fields in the value portion of a key-value pair DB.
As the illustration mentions, additional removal of BDB calls is required to isolate Subversion dependency on BDB. My original document listed modifications to Mike’s isolation work, which would eventually allow multiple FSs to exist under a common API. The next section addresses it in more detail.
In the goals section I stated that I wish to allow existing, modified, and completely re-written implementations of the FS to live together peacefully side by side under a common FS API. It’s important to note that I said FS not DB. FS improvements that make use of specific DB features may require the logic to branch at the API level. I believe that a Service Provider Interface is a good way to organize the FS going forward. Where the providers plug in at the FS API level. Please try to keep an open mind about this point until you’ve read this entire document. In figure 2 below, the blue and yellow layers can use the current FS API to communicate with a number of different FS implementations (providers). This is accomplished through a veneer vtable and a FS loader (not shown). In this brave new world the current FS implementation is called the BDB File System Provider (BDB FSP).
But wait; writing an FS provider to implement the entire FS API is no small undertaking. Why would anyone ever do that? They would have to be insane. One sure way to prevent the insanity would be to tightly couple the current implementation to the FS API. This is how the code is structured today. I say insanity is good. I say we can have our cake and eat it too. Just because we allow it to be done doesn’t mean that it’s required to be done that way. The reasons for allowing it should become clear if you read the entire document.
Getting back to the approach, look at figure 2; you’ll notice that the bulk of the FS logic has been moved to something called the “Baseline File System Abstract Provider” (Baseline FSAP). Before you get all wrapped around the axle conjuring up visions of abstract base classes in your favorite OO programming language, lets remember this is C, so lets loosen our collars and think of “Abstract Providers” in an abstract way:-). The implementation as proposed is really just lower level service provider interface. However, I believe thinking of it this way creates too narrow of a vision. So, think of the BDB FSP as providing the missing “concrete methods” for the Baseline FSAP and in doing so becomes a concrete FSP. Take a moment to review figure 2. The dashed arrowed lines again represent calls of protected functions. The little colored boxes are calls that cross the named boundaries. See the key for details.

A little history should be visited at this point. The changes outlined in figure 2 were submitted as one big patch. The patch didn’t move most of the files to their final resting spot. I tried it and had lots-o-update problems keeping it in sync with trunk. So that piece was left to a later patch. Being concerned about how big my patch was becoming, I decided to stop and post more changes later. Little did I know that my idea of incremental was way off the mark;-). The patch was labeled a “bomb blast” and later rejected in favor of a real incremental approach to applying the changes. One increment exists in trunk today. Another exists on the pluggable-fs branch. After some thought I decided that such a large change submitted as small increments requires a well-documented plan. Hopefully, this document meets this requirement. Need something similar up top.
Besides being too large to be properly reviewed, at least two people considered the veneer (API) level vtable unnecessary. The API vtable was a bit of a hack as implemented. Being concerned about the size of my patch, I stopped work on the API vtable prematurely. In retrospect, I should have removed that portion of the patch and presented it after I had a more mature implementation. However, I still feel strongly about the need for a higher-level “plug-in” point.
For reference Appendix A has the list of changes made in the master patch. However, reviewing the svn_fs_t structure (object) will best explain the focus of the patch. Table A outlines the changes I made to the structure and Appendix B contains the current trunk HEAD svn_fs_t and the most recent “master patch” version submitted.
|
Master_Patch field |
HEAD field |
Notes |
|
|
|
|
|
is_open |
N/A |
Temporary implementation detail; will be deleted. |
|
pool |
Pool |
Unchanged |
|
path |
Path |
Unchanged |
|
av |
N/A |
The API vtable that contains function pointers for the svn_fs API functions. |
|
fs_type |
N/A |
Unused. I believe this should be changed to a char field that will be used by the fs_loader to quickly identify the FS type of an open DB. |
|
db_cntxt |
env, changes, copies, nodes, … |
Cast and used only by DB specific routines. Within the baseline abstract provider it will never be accessed directly. This may need to be moved to impl_data. (Provider data) |
|
impl_data (provider data) |
N/A |
Sand box for fs providers to stash their implementation specific data. |
|
warning_baton |
warning_baton |
Yuck. |
|
config |
Config |
Currently has a contextual impedance problem. As it’s currently used, it should be a calling parameter not an attribute of this object. However, long-term a config item will be needed as an attribute so I’m ignoring this wart for nowJ |
Table A
The svn_fs_t object is central to the FS. It alone contains references to the “environment” used to store the data. “SVN transactions” and “Roots” maintain references to it. In the master patch version of this object, the environment is stored in db_cntxt. In the BDB FSP, it references a structure, which contains the required DB_ENV and DB constructs. Any other concrete implementations of the baseline FSAP can store similar information for its DB environment in the db_cntxt field.
The impl_data can be used by a FS provider to save other provider specific information. In the case of the baseline FSAP it is occupied by a vtable (Baseline DB Access vtable) used to access the API crafted into the BDB layer. With the exception of table opens, all the functions in the xxx-table.c files have no BDB specific arguments. This makes a reasonable place to plug in different DB back ends. The table open calls need to be hidden from the API for two reasons. First, they contain BDB specific parameters. Second, some DB environments don’t allow for opening specific tables. All the tables are opened and closed from single functions anyway. So this causes no real loss of granularity. As such, the opening and closing of the DB is called from the API level vtable. All FSP specific methods are installed in the FS initialization calls.
The “baseline db access” vtable is missing a small piece of the BDB access pie. Trail.c also makes calls into BDB. This had to be abstracted away. I feel trails can stand alone separate from the baseline FSAP so it was not made part of the “baseline db access” vtable. The changes were simple. Trail.c adds a “micro framework” on top of ACID transactions. It has three protected methods: svn_fs__record_completion and svn_fs__record_undo to register hooks for successful or unsuccessful completion of a transaction, and svn_fs__retry_txn to give control of the retry logic to trail.c. Any FS provider wishing to use trails must implement 5 methods. They are: begin_trail_txn,abort_trail_txn, commit_trail_txn, trail_is_recoverable_error, and flush_trail_txn. Consequently, because the Baseline FSAP uses trails, the BDB FSP assumes this responsibility. So where was this vtable placed? …
The final “major” piece of the patch pie is the API vtable or “av” in the svn_fs_t object. As mentioned some folks thought it to be unnecessary. It’s not unnecessary, just poorly implementedJ. It and the other members of this structure are in the cross hairs and are addressed later in this document. The structures as implemented don’t create all the proper divisions I was looking for. For example, the API vtable as previously implemented, not committed, actually implements two vtables, one for the public FS API and one for trails. This was always considered temporary. As I said before, it still needs some workJ.
Rather than spend more time discussing in depth the existing implementation details, and its flaws, I’d rather discuss the modifications I’m proposing now. To do so requires looking at the svn_fs API. So lets move on.
Below is an incomplete static UML diagram of the FS API. The goal isn’t to look at the implementation details. In fact the goal is quite the opposite. The more implementation details that creep into the FS API the more difficult pluggable back-ends will be to implement. The method contracts are a different story however. They need to be ridiculously detailed about behavior while being absent of implementation details. The Current Nits section covers some of the little things I think need to be cleaned up.
Before you examine the diagram I have a few quick notes. Only public methods are shown. Only the attributes exposed in some way are listed. I left out calling parameters; the boxes would have been rather large. Click on the image to see the svn_fs.h file. It has prototypes and descriptions of all the API methods. I make no distinction between function returns and “pass by reference”, return parameters. In the few cases where multiple “pass by reference” return parameters exist, I glue them together with “_n_” (See svn_fs_commit_txn). The silly color boxes are there because I couldn’t for the life of me figure out how to color specific methods. I just upgraded from Visio 5 and third party templates to Visio 2002 and it’s built in UML stuff. Does anyone know how to do this? It really pa-tissed me offL.

I assume the objects shown here don’t surprise anyone. Well FileSysUtil might, I just threw all the non-instance methods in there to get them out of my way. Whoa, what about some of those FileSys methods? They’re not instance methods. svn_fs_delete_fs and svn_fs_recover_fs don’t take the svn_fs_t object as an argument. Here again I’m asking for some leniency. They do operate on an instance of the FS. Actually, I want to change these methods to take an svn_fs_t object. I’ll discuss it in the API Visible Changes section. So hold your britches.
I want to make all these methods virtual. That’s what my original patch essentially did. Only now I’d like to have three vtables instead of one: One for FileSys, one for SVNTxn, and one for Root.
The svn_fs_open_fs, svn_fs_create_fs, svn_fs_create_txn, svn_fs_open_txn, svn_fs_revision_root, and svn_fs_txn_root are factory methods AKA virtual constructors. This allows the FS to decide which “subclass” of the object to create. Well, lets not call them subclasses. Lets call them File System Provider Objects (FSPO). They’re not really a subclass; they are merely implementing an interface.
At this point, I know some of you are saying “no way” and mumbling something about the API vtable. Please try to keep an open mind for the moment. I’m still proposing the “baseline db access” table so an FS provider writer doesn’t have to write to the FS API. And, I do recognize that there is potential for reuse/reimplementation between the FS API and the “baseline db access” vtable. I find myself in a bit of a catch-22. It’s hard for me to explain the why without the how. However, if you don’t know the why, you may not follow the how. So, if the next two sections seem disjointed; just read them twice. It’s like listing a lib twice to resolve circular referencesJ.
Lets see what this new organization will look like.
How much will this change the master patch organization? Not that much really. Figure 4 includes changes to figure 2 required to implement the new organization.

Previously I stated that all the public methods would be made virtual. In the case of object constructors, it’s a little more involved than that. Fs.c and fs-trans.c will implement the methods that construct objects. They will intern delegate some of the work to fs-loader. It will be responsible for providing the “vtable construction framework” that FS providers will hook into. A FSP specific constructor will be called using the vtable. The existing constructors, now part of the baseline FSAP, will be modified to accept two additional arguments: svn_config_t and pdata. The svn_config_t parameter can be null. The pdata can be null, sparsely populated or completely populated at the FSP writers choosing. This allows pdata population in the intializer and/or the constructor. Please don’t make me call it a batonJ. For those of you with immediate interest in the construction of objects, you can skip to FSP vtable initializers, but I recommend waiting.
Fs-loader.c will be implemented in two steps. The first incarnation will simply hard code calls into the BDB FSP initialization methods similar to way fs.c does in my original patch. It will pass null for svn_config_t. In its final form, information in a “ra_lib_defn” like structure will be combined with server configuration information to determine which FSP initialization methods will be called to initialize the FSPOs. The fs-loader will also provide configuration information to the initialization methods to give them access to FSP specific configuration information. FSP initialization methods are free to delegate portions of the initialization. Like calling Baseline FSAP initialization methods for example.
Before you start thinking this is overly complicated. Let me assure you that the changes are minimal and I know they will pay off over time. For example:
The biggest winner is the Root object. This is where some of the more meaty methods live. There are many different method implementation combinations possible even within an FSP implementation.
For starters, when a revision root is being constructed, all the methods defined in TransRoot can be set to a method that simply returns a consistent error message. So we can remove any existing “if not a transaction” tests allowing future FSP specific versions of the method to get a free and consistent error return.
Next, the virtual construction framework can be useful for invoking alternate method implementations for Revision and Transaction Roots. I believe that methods being called on a revision root don’t need transactions. Yes I know, those pesky deltifications of existing revs. Brane believes that a lock extension to trails could improve the performance of read-only methods for the BDB FSP. For SQL DBs, that implement consistent reads based on multi-versioning, the DB transaction wrapped by the trail could be rendered impotent via read only settings for example. In these two cases, altered versions of the outer method calling the “txn_body_xxx” function can be developed and installed selectively by the virtual construction framework. OR, completely new versions of the methods could be developed which don’t use trails at all. It’s important to note that trails, in my mind, are one of the biggest obstacles for FSP implementers considering re-use of the baseline FSAP. It makes writing “performance tweaked” methods quite difficult in my opinion. I do like the construct. It simply makes re-use of the middle portions of the baseline FSAP layer more restrictive. Another has to do with storage of directory and property data. That is discussed in detail below.
The idea of writing new methods at the API level is where some people get a little nervous. A quote from gstein “I don't really see a need to change things at that level; the probability for changing the semantics is just too great, leading to problems down the line. Just how much rope do we want to provide?” My answer: As much as they want, use documentation and the test suite to identify semantic differences. The key is not to allow the existing BDB FS to be adversely impacted by change. In fact, this is precisely why we need vtables at the API level. Over time a SQL based FS and a “BDB like” DB will have conflicting goals. Continuing to force the two co-exist at a fine grain level will create code churn that otherwise wouldn’t exist. We’d be constantly balancing the needs of one against the other. Ridiculous compromises will ensue. OH the horror of it allJ. GAT puts away his crystal ball.
Changes made deeper in the FS code can still cause method behavioral changes that go unnoticed until users at large begin to use the new code. Having an API level vtables will prove particularly useful when developing experimental methods. They can be written and shared. Savvy end users and developers can try them with minimal impact to the sanctioned FS.
Surely I believe that a middle layer API could be vtable-fied. Right? Yes I do. And I think the API vtables are one way to wick them out. Hun? I think the examples might make this point better. However, I believe that any middle level vtable(s) should become the responsibility of an FSAP just as the DB access vtable will be. The outer framework should know nothing about it. Why? Because any object hierarchy, whether it has abstract components or not, will impose certain constraints. These constraints are necessary to keep order. The problem is that I believe there will be at least two camps forming within the FS; one that is highly procedural and one that pushes more logic to the DB. Differing DB isolation protection may form still more camps. As I said before, they will begin to conflict. All will have their own constraints required to keep order. Two or more abstract providers can be developed to deal with the divergent constraints while still allowing for different storage backends within them.
Not convinced about the conflicts? Consider this. Roundtrips kill performance. This isn’t news to anyone working on Subversion. Look at all the effort put into reducing roundtrips between the client and the server. Roundtrips kill SQL DBs also, especially in a multi-tiered environment. I don’t know too many DBAs that are keen on having their machines at tier 1 or 2. So we have a multi-tiered situation. Yes proxies can help here, but multi-tiered or not, it’s still an issue. Check out svn_fs_is_dir or svn_fs_is_file. The first time I looked at those two methods I said: “WTF”. I expected a simple “accessor like” method. Silly me. I put on a dag hat and the fog began to clearJ. In a SQL DBMS every call to the DB creates considerable overhead. I’m not picking on those methods. I’m just pointing out that many things being done procedurally in the current FS will be faster if they can be done in a stored procedure or in “mega” queries. Also, if a lot of interim data is being processed, even temp tables solutions beat application based procedural solutions in many cases. None of this is trivial work, it must be done in a way that doesn’t disrupt the baseline implementation. This is where many say the work should be done in a branch. Changes of this magnitude run the risk of dieing on the vine.
How many people actually compiled and tried Bill Tutt’s issue 1003 changes? What if Bill could have built that code in a way that could be installed dynamically so people could try it along side the current implementation? What if the code could be installed with a configuration switch and this switch could be flipped and stored with the repository at creation time. Hey, now I can have different DB/schemas accessed from within the same binary. The repository determines which version of the code to run. At a minimum, independent testing should go up. People can start testing something that isn’t quite ready, without building a branch. On the down side, this technique will result in more copy/pasting to avoid naming conflicts and will minimize code reuse in initial patches. However, when ready, the changes can be merged back in to existing “sanctioned abstract providers” if it’s appropriate. As I recall, Bill’s changes resulted in existing methods returning different results than before. By my understanding that alters the existing API contracts. If it were pluggable, this change could be more easily tested against trunk code to flesh out subtle impacts. No merge required to do it.
I mentioned the implementation camps I believe will form. What about user camps? I see at least three:
1. The purists. They want nothing to do with DBMSs at all.
2. The pragmatists. They will prefer a file system approach but are willing to accept a minimal amount of DB administration. They accept the assertion that DB transactions are required for atomic commits.
3. The enterprisers. They believe that Subversion will some day rule the worldJ. This camp believes that everything needs to be version controlled. This group will only be happy when terabyte repositories exist in vast numbers.
In my opinion, these camps will tug and pull on each other like there’s no tomorrow. I think they can all play together as long as the FS API is kept unbiased. Whenever implementation conflicts arise they can diverge only as far as the API will allow. This is why the contracts in svn_fs.h need to be gone over and over and over. It will become the tiebreaker.
The following comment from dag.h is also quite telling. There have to be a few different approaches between svn_fs.h and dag.h no?
/* The interface in this file provides all the essential filesystem
operations, but exposes the filesystem's DAG structure. This makes
it simpler to implement than the public interface, since a client
of this interface has to understand and cope with shared structure
directly as it appears in the database. However, it's still a
self-consistent set of invariants to maintain, making it
(hopefully) a useful interface boundary.
In other words:
- The dag_node_t interface exposes the internal DAG structure of
the filesystem, while the svn_fs.h interface does any cloning
necessary to make the filesystem look like a tree.
- The dag_node_t interface exposes the existence of copy nodes,
whereas the svn_fs.h handles them transparently.
- dag_node_t's must be explicitly cloned, whereas the svn_fs.h
operations make clones implicitly.
- Callers of the dag_node_t interface use Berkeley DB transactions
to ensure consistency between operations, while callers of the
svn_fs.h interface use Subversion transactions. */
Enough already, can someone please hand me a rope. I’m going to hang myself if I don’t get this higher-level vtable.
To implement the new organization a few structures have to change. Those structures are included in Appendix C. Basically, svn_fs_t, svn_fs_txn_t, and svn_fs_root_t all get vtables and provider data (pdata PKA impl_data). Appendix C also includes svn_fs__bl_fsap_pdata and its subordinates, which is the new pdata structure to be used by the baseline FSAP and the concrete FSPs, which are based on it. The DB context, baseline DB access vtable, and trail vtable all live there now. Referring to Appendix B should help to clarify the evolution from trunk to this latest proposal.
In addition some structures will be moved around to better organize them along the new boundaries. Table B details the moves.
|
Structure Name |
Was in |
New Name |
Now in |
|
|
|
|
|
|
svn_fs__revision_t |
fs.h |
svn_fs__bl_ap_rev_t |
fs-bl-ap.h |
|
svn_fs__transaction_t |
fs.h |
svn_fs__bl_ap_txn_t |
fs-bl-ap.h |
|
svn_fs__node_revision_t |
fs.h |
svn_fs__bl_node_rev_t |
fs-bl-ap.h |
|
svn_fs__rep_kind_t |
fs.h |
svn_fs__bl_rep_kind_t |
fs-bl-ap.h |
|
svn_fs__rep_delta_chunk_t |
fs.h |
svn_fs__bl_rep_delta_chunk_t |
fs-bl-ap.h |
|
svn_fs__representation_t |
fs.h |
svn_fs__bl_rep_t |
fs-bl-ap.h |
|
svn_fs__copy_t |
fs.h |
svn_fs__bl_copy_t |
fs-bl-ap.h |
|
svn_fs__change_t |
fs.h |
svn_fs__bl_chg_t |
fs-bl-ap.h |
Table B
Generalizing the dag_node_t in svn_fs_t????
In my original patch, all the methods exposed to the FS API had their name altered to include an additional “_”. svn_fs_close_root became svn_fs__close_root and so on. This was a mistake. In this version I want to change the prefix to svn_fs__bl. Many of the other source files, like dag.c, have well adhered to prefixing. In some of the other files, packaging isn’t quite as clear. Perhaps a quick name sweep is in order. Any new FSAP(s), or FSP(s) for that matter, need to identify and use a well-known prefix.
Adding new methods for storage of properties and directories that take hashes not skels. Required for generation one of the SQL FS. What makes this a fair amount of work is the assumption, that properties and directories are stored as string blobs, by the deltification code.
Scratch pool management now needs to be more robust. I will be re-proposing a previous mailing along with a working implementation.
Where to begin with configuration? Hmmm. Lets start with what we have in place. Not so long ago a hash was added to the svn_fs_new method. Currently, it’s only used for the svnadmin “--bdb-txn-nosync” option. If this option is specified, svnadmin creates an apr_hash_t, sets the key SVN_FS_CONFIG_BDB_TXN_NOSYNC to “1” and passes it to svn_repos_create. This hash is forwarded on to svn_fs_new. Later, svn_fs_create_berkeley checks for this key and, if set, it places “set_flags DB_TXN_NOSYNC” in the BDB configuration file.
At this point it’s important to understand that configurability is the corner stone of the changes I’m proposing. If I don’t get what I want, I’m afraid I’m going to have to tell my mom.L
The existing hash is okay but I’d really like some namespaces. I’m happy to share the sand box with other FS implementers, but I don’t want them putting flags on my castleJ. So I propose we use svn_config_t instead of apr_hash_t. My long-term plans for a SQL FS are quite elaborate so I want to add an option to svnadmin, which takes a config file, rather than clutter the svnadmin command line. This file is read in via svn_config_read and passed to svn_repos_create just as the hash is now. Template config files will be provided for common configurations.
Using svn_config_t poses a small problem for the “—bdb-txn-nosync” option because there is no method for creating a new svn_config_t programmatically. It’s an easy change though. In fact I have a local patch for theses configuration changes. It is part of the roadmap but I’m happy to patch against trunk if someone is willing to commit it.
While I’m on the subject of changes, I would like to make one more small change to svn_config. Since the config is provided at repository creation time, it’s reasonable to expect that the information contained in it may need to be saved and used upon subsequent opens. In fact it’s a feature required for the fs-loader to support vtable initializers. Originally, I was going to propose adding a method to svn_config to serialize it. After some thought, it occurred to me that only certain portions need to be saved. What I would like to see now is a local mod I made for my SQL stuff. All I need is a public method to get all the sections in the config. I have other evil plans that require this simple change. Most are specific to my SQL stuff, which is beyond the scope of this document. Oh, and don’t worry, I won’t be reading the configs every time I construct an object, only at open time.
There are three vtable initializers that must be implemented by every FSP. They are invoked, via the fs loader, prior to object creation for the FileSys, SVNTxn, and Root objects. It needs to be noted that the pdata argument does not have to be populated by the initializer, if it is, it can be sparsely populated. The object constructor can safely assume that the initializer is always called prior to object construction. Also, I see no problems with initializers returning pointers to static vtables.
The name “vtable initializers” may be a little misleading. They’re called that because it’s the only required function they perform. The FS implementer is free to do other evil things with the pdata. One of which could be to initialize FSAP/FSP specific vtables. This is exactly what the BDB FSP will do to meet baseline FSAP requirements.
Some might be wondering why I have a separate initialize call that precede the object create. The short answer is that IMO it’s more flexible than having factory like object construction controlled by an object hierarchies with potentially varying goals.
The longer answer is that not having it will force FSAPs to implement their own construction framework. My way, FSAPs can provide mandatory or optional convenience methods that FSPs can use in their constructors. The outer framework implemented by the combination of the fs-loader.c, fs.c, fs-root.c, and fs-trans.c can invoke FSP constructors directly. Which is required for anyone wanting to reinvent the entire wheel (not using a FSAP). A separate initialization step is required to do this in an opaque way. Initialization requirements of FSAPs can be worked out between the initializers and constructors. Let FSAP and FSP implementers work out their own policies with regard to what should be performed and when. One area where I think this may be useful long-term has to do with bootstrapping from a DB. I believe there will be other areas where it makes sense to separate initialization from construction.
Maybe an activity or state diagram would help here?
In addition to the changes I want to make beneath the API, there are a few changes I’d like to make at the FS API level.
First up is svn_fs_new. I have always wondered what the purpose of this method is. There is nothing in there that can’t be done in svn_fs_create_berkeley and svn_fs_open_berkeley. Let’s add the config parameter to the create method, the parent_pool to the open and the create methods, and kill the silly thing. This should make call_functions_with_unopened_fs in fs-test.c obsolete. Which leads me to the second change. .
All methods containing Berkeley in the name need to be replaced with “fs”, with one exception, set_Berkeley_errcall. This is never called. I understand its purpose. If this sort of information is needed for tracking down specific DB issues, I suggest that we consider creating a FS logging feature instead. Obviously, this would require a bit of discussion but it has broader potential. I’d like to kill this method for now. Later, if it’s needed to track down a specific problem, a logging feature can be developed in its place. I have submitted a patch to the FS code, which will glue callback based error messages into the svn_error_t error change, when a DB error occurs. ** This was rejected due to my marshalling of a pointer to safe guard against BDB unknowns.
While we have out the hatchet, svn_fs_deltify and svn_fs_undeltify should be removed from the API. What you say? They are called only from tests. I propose we do one of two things: First replace the two methods with one called “svn_fs_optimize”. It would take one additional argument specifying to optimize for time or space. It could be implemented as a noop. Second, we could create a maintenance API whose methods are optionally implemented by FSPs. Svn_fs_recover also falls into this maintenance category. No, I’m not proposing we delete it.J But, it is a maintenance method and can be a noop for certain FSPs. In larger shops, these methods will need to be carefully guarded. I think we should “open” the FS in a maintenance mode for functions like these. Which leads me to my next API visible change.
I believe that the svn_fs_open needs a “flag” parameter. I want it to work much the same as its file/device namesake. I would like to have flags like SVN_FS_RDONLY, SVN_FS_EXCLUSIVE, etc. SVN_FS_EXCLUSIVE could be used to replace the current lock acquired before repos recovery. This would allow FSP authors to create a distributed implementation if needed. Certain FS implementations may be able to take advantage of a particular flag while others can safely ignore it. SVN_FS_EXCLUSIVE could not be safely ignored of course. As for SVN_FS_RDONLY, I think there is some performance opportunity there. I do not intend for SVN_FS_RDONLY to mean “doesn’t write to DB for checkouts and updates”. The intent is to indicate that the user of this FS object will not be creating any transaction roots. I still need to quantify what this really means.
svn_fs_recover and svn_fs_delete need to become real instance methods IMHO. In a SQL backed FS the svn_fs_recover method will need to connect to the DB in order to drop the tables. Also, assuming we add a “flag” parameter to svn_fs_open and the SVN_FS_EXCLUSIVE mask, the open will be needed to lock users out of the FS while the recovery is being performed. As for the svn_fs_delete method, we want to make sure we block on any pending user updates so we get the opportunity to trash everyone’s updates:-). Seriously though, it will need to connect to the DB to do the delete even though acquiring an exclusive lock does seem a bit silly.
To better illustrate the proposed infrastructure, I think some examples are in order. I’ll start with one that touches each of the pluggable components required to create an FSP using the API level hooks and the baseline FSAP. Then I’ll venture into the types of activities that will result in mid-level vtables and alternate FSAPs. And finally I’ll discuss how a shy yet evil mastermind can get in on the action.
Let’s assume that the folks at Sleepy Cat have some new competition. A company called “Wired dog” claims to have a BDB like DB that is faster (or whatever). Apparently, they are unable to let sleeping cats lye. Da dant tssss:-). Anyway, this is what is required to get the job done. This example will be a renamed version of the BDB methods. Allows me to kill two birds with one stone.
What the constructor framework requires:
What the baseline FSAP requires:
What the FSP will have to provide:
What the baseline FSAP provides to assist:
Adding a virtual method to the FSAP.
Adding a virtual method to the FSAP from the FS API on down. This guy proves his point by making changes that are completely new from the API level down.
The building or growing of a new FSAP.
Secret and semi-secret development.
The road map is basically the incremental steps agreed upon between Cmike and myself with the new changes mixed in and highlighted in the places where it is different.
struct svn_fs_t
{
/* A pool managing this filesystem. Freeing this pool must
completely clean up the filesystem, including any database
or system resources it holds. */
apr_pool_t *pool;
/* The path to the repository's top-level directory. */
char *path;
/* A Berkeley DB environment for all the filesystem's databases.
This establishes the scope of the filesystem's transactions. */
DB_ENV *env;
/* The filesystem's various tables. See `structure' for details. */
DB *changes;
DB *copies;
DB *nodes;
DB *representations;
DB *revisions;
DB *strings;
DB *transactions;
DB *uuids;
/* A callback function for printing warning messages, and a baton to
pass through to it. */
svn_fs_warning_callback_t warning;
void *warning_baton;
/* The filesystem configuration. */
apr_hash_t *config;
};
struct svn_fs_t
{
/* This field is put in to handle quick and dirty file system is_open checks.
The more detailed checks still exist below the veneer layer.
The veneer vtable will be full of nulls if the fs has never been opened.
So the code will blow up in ways it would not before vtables.
The fs structure *MUST* be created by the fs open and fs create calls.
If it is not Seg faults will occur when referencing functions via
the API/veneer layer vtable.
*/
int is_open;
/* A pool managing this filesystem. Freeing this pool must
completely clean up the filesystem, including any database
or system resources it holds. */
apr_pool_t *pool;
/* The path to the repository's top-level directory. */
char *path;
/* used for API level calls and functions needed to support it */
svn_fs__api_vtab_t *av;
int fs_type;
/* FS type specific structures */
/* This is used by db-specific fs routines */
/* This could be part of the impl_data but I chose to keep it separate
because two different implementations could use the same db_contx struct
under the sheets */
void *db_cntxt;
/* Opaque data structure for the FS implementor to use */
void *impl_data;
/* A callback function for printing warning messages, and a baton to
pass through to it. */
svn_fs_warning_callback_t warning;
void *warning_baton;
/* The filesystem configuration. */
apr_hash_t *config;
};
struct svn_fs_t
{
/* A pool managing this filesystem. Freeing this pool must
completely clean up the filesystem, including any database
or system resources it holds. */
apr_pool_t *pool;
/* The path to the repository's top-level directory. */
char *path;
/* Official name of the FSP. Used by loader. */
char *fsp_nm;
/* used for API level calls and functions needed to support it */
svn_fs__fs_vtab_t *fv;
/* A callback function for printing warning messages, and a baton to
pass through to it */
svn_fs_warning_callback_t warning;
void *warning_baton;
/* The filesystem configuration */
svn_config_t *config;
/* Opaque data structure for the FS provider to use */
void *pdata;
};
struct svn_fs__fs_vtab_t
{
svn_error_t *(*create_fs) (svn_fs_t *fs, const char *path, svn_config_t *cfg);
svn_error_t *(*open_fs) (svn_fs_t *fs, const char *path, const int flag, svn_config_t *cfg);
svn_error_t *(*set_berkeley_errcall)(svn_fs_t *fs,
void (*db_errcall_fcn) (const char *errpfx,
char *msg));
void (*set_warning_func) (svn_fs_t *fs,svn_fs_warning_callback_t warning,
void *warning_baton);
const char *(*get_path) (svn_fs_t *fs, apr_pool_t *pool);
svn_error_t *(*set_errcall) (svn_fs_t *fs,
void (*handler) (const char *errpfx,
char *msg));
svn_error_t *(*delete_fs) (const char *PATH, apr_pool_t *pool);
/* Can be noop */
svn_error_t *(*recover_fs) (const char *path, apr_pool_t *pool);
char *msg));
/* This may be made non virtual or noop */
void (*set_warning_func) (svn_fs_t *fs,I
svn_fs_warning_callback_t warning,
void *warning_baton);
svn_error_t *(*youngest_rev) (svn_revnum_t *youngest_p, svn_fs_t *fs,
apr_pool_t *pool);
svn_error_t *(*revision_prop) (svn_string_t **value_p, svn_fs_t *fs,
svn_revnum_t rev, const char *propname,
apr_pool_t *pool);
svn_error_t *(*revision_proplist) (apr_hash_t **table_p, svn_fs_t *fs,
svn_revnum_t rev, apr_pool_t *pool);
svn_error_t *(*change_rev_prop) (svn_fs_t *fs, svn_revnum_t rev,
const char *name,
const svn_string_t *value,
apr_pool_t *pool);
svn_error_t *(get_uuid) (svn_fs_t *fs, const char **uuid, apr_pool_t *pool);
svn_error_t *(set_uuid) (svn_fs_t *fs, const char *uuid,
apr_pool_t *pool);
svn_error_t *(*revision_root) (svn_fs_root_t **root_p, svn_fs_t *fs,
svn_revnum_t rev, apr_pool_t *pool);
svn_error_t *(*begin_txn) (svn_fs_txn_t **txn_p, svn_fs_t *fs,
svn_revnum_t rev, svn_config_t *cfg,
void *pdata, apr_pool_t *pool);
svn_error_t *(*open_txn) (svn_fs_txn_t **txn, svn_fs_t *fs,
const char *name, svn_config_t *cfg,
void *pdata, apr_pool_t *pool);
svn_error_t *(*list_transactions) (apr_array_header_t **names_p,
svn_fs_t *fs, apr_pool_t *pool);
};
/* Baseline FS abstract provider's pdata */
struct svn_fs__bl_fsap_pdata
{
/* This is used by db-specific fs routines for storing DB info */
void *db_cntxt;
svn_fs__trl_vtab_t *tv;
svn_fs__bl_fsap_db_funcs_t *dbv;
/* opaque storage for FSPs implementing the BL FSAP */
void *fsp_pdata;
};
/* BDB FSP?s db_context. (Never referenced above bdb level */
struct svn_fs__bdb_cntxt_t
{
/* A Berkeley DB environment for database.
This establishes the scope of the filesystem's transactions. */
DB_ENV *env;
/* The filesystem's various tables. See `structure' for details. */
DB *changes;
DB *copies;
DB *nodes;
DB *representations;
DB *revisions;
DB *strings;
DB *transactions;
DB *uuids;
};
/* Trail vtable */
struct svn_fs__trl_vtab_t
{
svn_error_t *(*begin_trail_txn) (svn_fs_t *fs, trail_t *trail_p);
svn_error_t *(*abort_trail_txn) (svn_fs_t *fs, trail_t *trail_p);
svn_error_t *(*commit_trail_txn) (svn_fs_t *fs, trail_t *trail_p);
svn_boolean_t (*trail_recoverable_error) (svn_error_t *svn_err,
svn_fs_t *fs, trail_t *trail_p);
/* can be noop */
svn_error_t *(*flush_trail_txn) (svn_fs_t *fs, trail_t *trail_p);
};
struct svn_fs__bl_fsap_db_funcs_t
{
/* Transaction table */
/* Create a new transaction in FS as part of TRAIL, with an initial
root and base root ID of ROOT_ID. Set *TXN_NAME_P to the name of the
new transaction, allocated in TRAIL->pool. */
svn_error_t *(*create_txn) (const char **txn_name_p, svn_fs_t *fs,
const svn_fs_id_t *root_id, trail_t *trail);
/* Remove the transaction whose name is TXN_NAME from the `transactions'
table of FS, as part of TRAIL.
Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
transaction that has already been committed. */
svn_error_t *(*delete_txn) (svn_fs_t *fs, const char *txn_name,
trail_t *trail);
/* Retrieve the transaction *TXN_P for the Subversion transaction
named TXN_NAME from the `transactions' table of FS, as part of
TRAIL. Perform all allocations in TRAIL->pool.
If there is no such transaction, SVN_ERR_FS_NO_SUCH_TRANSACTION is
the error returned. */
svn_error_t *(*get_txn) (svn_fs__transaction_t **txn_p, svn_fs_t *fs,
const char *txn_name, trail_t *trail);
/* Store the Suversion transaction TXN in FS with an ID of TXN_NAME as
part of TRAIL. */
svn_error_t *(*put_txn) (svn_fs_t *fs, const svn_fs__transaction_t *txn,
const char *txn_name, trail_t *trail);
/* Set *NAMES_P to an array of const char * IDs (unfinished
transactions in FS) as part of TRAIL. Allocate the array and the
names in POOL, and use TRAIL->pool for any temporary allocations. */
svn_error_t *(*get_txn_list) (apr_array_header_t **names_p, svn_fs_t *fs,
apr_pool_t *pool, trail_t *trail);
/* Strings table */
/* Read *LEN bytes into BUF from OFFSET in string KEY in FS, as part
* of TRAIL.
*
* On return, *LEN is set to the number of bytes read. If this value
* is less than the number requested, the end of the string has been
* reached (no error is returned on end-of-string).
*
* If OFFSET is past the end of the string, then *LEN will be set to
* zero. Callers which are advancing OFFSET as they read portions of
* the string can terminate their loop when *LEN is returned as zero
* (which will occur when OFFSET == length(the string)).
*
* If string KEY does not exist, the error SVN_ERR_FS_NO_SUCH_STRING
* is returned.
*/
svn_error_t *(*string_read) (svn_fs_t *fs, const char *key, char *buf,
apr_off_t offset, apr_size_t *len,
trail_t *trail);
/* Set *SIZE to the size in bytes of string KEY in FS, as part of
* TRAIL.
*
* If string KEY does not exist, return SVN_ERR_FS_NO_SUCH_STRING.
*/
svn_error_t *(*string_size) (apr_size_t *size, svn_fs_t *fs, const char *key,
trail_t *trail);
/* Strings table */
/* Read *LEN bytes into BUF from OFFSET in string KEY in FS, as part
* of TRAIL.
*
* On return, *LEN is set to the number of bytes read. If this value
* is less than the number requested, the end of the string has been
* reached (no error is returned on end-of-string).
*
* If OFFSET is past the end of the string, then *LEN will be set to
* zero. Callers which are advancing OFFSET as they read portions of
* the string can terminate their loop when *LEN is returned as zero
* (which will occur when OFFSET == length(the string)).
*
* If string KEY does not exist, the error SVN_ERR_FS_NO_SUCH_STRING
* is returned.
*/
svn_error_t *(*string_read) (svn_fs_t *fs, const char *key, char *buf,
apr_off_t offset, apr_size_t *len,
trail_t *trail);
/* Set *SIZE to the size in bytes of string KEY in FS, as part of
* TRAIL.
*
* If string KEY does not exist, return SVN_ERR_FS_NO_SUCH_STRING.
*/
svn_error_t *(*string_size) (apr_size_t *size, svn_fs_t *fs, const char *key,
trail_t *trail);
/* Append LEN bytes from BUF to string *KEY in FS, as part of TRAIL.
*
* If *KEY is null, then create a new string and store the new key in
* *KEY (allocating it in TRAIL->pool), and write LEN bytes from BUF
* as the initial contents of the string.
*
* If *KEY is not null but there is no string named *KEY, return
* SVN_ERR_FS_NO_SUCH_STRING.
*
* Note: to overwrite the old contents of a string, call
* svn_fs__string_clear() and then svn_fs__string_append(). */
svn_error_t *(*string_append) (svn_fs_t *fs, const char **key,
apr_size_t len, const char *buf,
trail_t *trail);
/* Make string KEY in FS zero length, as part of TRAIL.
* If the string does not exist, return SVN_ERR_FS_NO_SUCH_STRING.
*/
svn_error_t *(*string_clear) (svn_fs_t *fs, const char *key,
trail_t *trail);
/* Delete string KEY from FS, as part of TRAIL.
*
* If string KEY does not exist, return SVN_ERR_FS_NO_SUCH_STRING.
*
* WARNING: Deleting a string renders unusable any representations
* that refer to it. Be careful.
*/
svn_error_t *(*string_delete) (svn_fs_t *fs, const char *key,
trail_t *trail);
/* Copy the contents of the string referred to by KEY in FS into a new
* record, returning the new record's key in *NEW_KEY. All
* allocations (including *NEW_KEY) occur in TRAIL->pool. */
svn_error_t *(*string_copy) (svn_fs_t *fs, const char **new_key,
const char *key, trail_t *trail);
/* Storing and retrieving filesystem revisions. */
/* Set *REVISION_P to point to the revision structure for the
filesystem revision REV in FS, as part of TRAIL. Perform all
allocations in TRAIL->pool. */
svn_error_t *(*get_rev) (svn_fs__revision_t **revision_p, svn_fs_t *fs,
svn_revnum_t rev, trail_t *trail);
/* Store REVISION in FS as revision *REV as part of TRAIL. If *REV is
an invalid revision number, create a brand new revision and return
its revision number as *REV to the caller. Do any necessary
temporary allocation in TRAIL->pool. */
svn_error_t *(*put_rev) (svn_revnum_t *rev, svn_fs_t *fs,
const svn_fs__revision_t *revision, trail_t *trail);
/* Set *YOUNGEST_P to the youngest revision in filesystem FS,
as part of TRAIL. Use TRAIL->pool for all temporary allocation. */
svn_error_t *(*youngest_rev) (svn_revnum_t *youngest_p, svn_fs_t *fs,
trail_t *trail);
/*** Storing and retrieving reps. ***/
/* Set *REP_P to point to the representation for the key KEY in
FS, as part of TRAIL. Perform all allocations in TRAIL->pool.
If KEY is not a representation in FS, the error
SVN_ERR_FS_NO_SUCH_REPRESENTATION is returned. */
svn_error_t *(*read_rep) (svn_fs__representation_t **rep_p, svn_fs_t *fs,
const char *key, trail_t *trail);
/* Store REP as the representation for KEY in FS, as part of
TRAIL. Do any necessary temporary allocation in TRAIL->pool. */
svn_error_t *(*write_rep) (svn_fs_t *fs, const char *key,
const svn_fs__representation_t *rep,
trail_t *trail);
/* Store REP as a new representation in FS, and the new rep's key in
*KEY, as part of trail. The new key is allocated in TRAIL->pool. */
svn_error_t *(*write_new_rep) (const char **key, svn_fs_t *fs,
const svn_fs__representation_t *rep,
trail_t *trail);
/* Delete representation KEY from FS, as part of TRAIL.
WARNING: This does not ensure that no one references this
representation! Callers should ensure that themselves. */
svn_error_t *(*delete_rep) (svn_fs_t *fs, const char *key,
trail_t *trail);
/* Check FS's `nodes' table to find an unused node number, and set
*ID_P to the ID of the first revision of an entirely new node in
FS, created in transaction TXN_ID, as part of TRAIL. Allocate the
new ID, and do all temporary allocation, in TRAIL->pool. */
svn_error_t *(*new_node_id) (svn_fs_id_t **id_p, svn_fs_t *fs,
const char *node_id, const char *txn_id,
trail_t *trail);
/* Delete node revision ID from FS's `nodes' table, as part of TRAIL.
WARNING: This does not check that the node revision is mutable!
Callers should do that check themselves.
todo: Jim and Karl are both not sure whether it would be better for
this to check mutability or not. On the one hand, having the
lowest level do that check would seem intuitively good. On the
other hand, we'll need a way to delete even immutable nodes someday
-- for example, someone accidentally commits NDA-protected data to
a public repository and wants to remove it. Thoughts? */
svn_error_t *(*delete_nodes_entry) (svn_fs_t *fs, const svn_fs_id_t *id,
trail_t *trail);
/* Set *SUCCESSOR_P to the ID of an immediate successor to node
revision ID in FS that does not exist yet, as part of TRAIL.
Allocate *SUCCESSOR_P in TRAIL->pool.
Use the current Subversion transaction name TXN_ID, and optionally
a copy id COPY_ID, in the determination of the new node revision
ID. */
svn_error_t *(*new_successor_id) (svn_fs_id_t **successor_p, svn_fs_t *fs,
const svn_fs_id_t *id, const char *copy_id,
const char *txn_id, trail_t *trail);
/* Set *NODEREV_P to the node-revision for the node ID in FS, as
part of TRAIL. Do any allocations in TRAIL->pool. Allow NODEREV_P
to be NULL, in which case it is not used, and this function acts as
an existence check for ID in FS. */
svn_error_t *(*get_node_revision) (svn_fs__node_revision_t **noderev_p,
svn_fs_t *fs, const svn_fs_id_t *id,
trail_t *trail);
/* Store NODEREV as the node-revision for the node whose id
is ID in FS, as part of TRAIL. Do any necessary temporary
allocation in TRAIL->pool.
After this call, the node table manager assumes that NODE's
contents will change frequently. */
svn_error_t *(*put_node_revision) (svn_fs_t *fs, const svn_fs_id_t *id,
svn_fs__node_revision_t *noderev,
trail_t *trail);
/* Reserve a slot in the `copies' table in FS for a new copy operation
as part of TRAIL. Return the slot's id in *COPY_ID_P, allocated in
TRAIL->pool. */
svn_error_t *(*reserve_copy_id) (const char **copy_id_p, svn_fs_t *fs,
trail_t *trail);
/* Create a new copy with id COPY_ID in FS as part of TRAIL.
SRC_PATH/SRC_TXN_ID are the path/transaction ID (respectively) of
the copy source, and DST_NODEREV_ID is the node revision id of the
copy destination.
SRC_PATH is expected to be a canonicalized filesystem path (see
svn_fs__canonicalize_abspath).
COPY_ID should generally come from a call to svn_fs__reserve_copy_id(). */
svn_error_t *(*create_copy) (const char *copy_id, svn_fs_t *fs,
const char *src_path, const char *src_txn_id,
const svn_fs_id_t *dst_noderev_id,
trail_t *trail);
/* Remove the copy whose name is COPY_ID from the `copies' table of
FS, as part of TRAIL. */
svn_error_t *(*delete_copy) (svn_fs_t *fs, const char *copy_id,
trail_t *trail);
/* Retrieve the copy *COPY_P named COPY_ID from the `copies' table of
FS, as part of TRAIL. Perform all allocations in TRAIL->pool.
If there is no such copy, SVN_ERR_FS_NO_SUCH_COPY is the error
returned. */
svn_error_t *(*get_copy) (svn_fs__copy_t **copy_p, svn_fs_t *fs,
const char *copy_id, trail_t *trail);
/* Add CHANGE as a record to the `changes' table in FS as part of
TRAIL, keyed on KEY.
CHANGE->path is expected to be a canonicalized filesystem path (see
svn_fs__canonicalize_abspath).
Note that because the `changes' table uses duplicate keys, this
function will not overwrite prior additions that have the KEY
key, but simply adds this new record alongside previous ones. */
svn_error_t *(*changes_add) (svn_fs_t *fs, const char *key,
svn_fs__change_t *change,
trail_t *trail);
/* Remove all changes associated with KEY from the `changes' table in
FS, as part of TRAIL. */
svn_error_t *(*changes_delete) (svn_fs_t *fs, const char *key,
trail_t *trail);
/* Return a hash *CHANGES_P, keyed on const char * paths, and
containing svn_fs_path_change_t * values representing summarized
changed records associated with KEY in FS, as part of TRAIL.
Allocate the array and its items in TRAIL->pool. */
svn_error_t *(*changes_fetch) (apr_hash_t **changes_p, svn_fs_t *fs,
const char *key, trail_t *trail);
/* Return an array *CHANGES_P of svn_fs__change_t * items representing
all the change records associated with KEY in FS, as part of TRAIL.
Allocate the array and its items in TRAIL->pool. */
svn_error_t *(*changes_fetch_raw) (apr_array_header_t **changes_p,
svn_fs_t *fs,
const char *key, trail_t *trail);
/* Get the UUID at index @a idx in the uuids table within @a fs,
* storing the result in @a *uuid.
*/
svn_error_t *(*get_uuid) (svn_fs_t *fs, int idx, const char **uuid,
trail_t *trail);
/* Set the UUID at index @a idx in the uuids table within @a fs
* to @a uuid.
*/
svn_error_t *(*set_uuid) (svn_fs_t *fs, int idx, const char *uuid,
trail_t *trail);
};
struct svn_fs_txn_t
{
/* This transaction's private pool, a subpool of fs->pool.
Freeing this must completely clean up the transaction object,
write back any buffered data, and release any database or system
resources it holds. (But don't confuse the transaction object
with the transaction it represents: freeing this does *not* abort
the transaction.) */
apr_pool_t *pool;
/* The filesystem to which this transaction belongs. */
svn_fs_t *fs;
/* The revision on which this transaction is based, or
SVN_INVALID_REVISION if the transaction is not based on a
revision at all. */
svn_revnum_t base_rev;
/* The ID of this transaction --- a null-terminated string.
This is the key into the `transactions' table. */
const char *id;
/* used for API level calls and functions *required* to support it */
svn_fs__txn_vtab_t *tv;
/* Opaque data structure for the FS provider to use */
void *pdata;
};
struct svn_fs__txn_vtab_t
{
svn_error_t *(*commit_txn) (const char **conflict_p,
svn_revnum_t *new_rev, svn_fs_txn_t *txn);
svn_error_t *(*abort_txn) (svn_fs_txn_t *txn);
svn_error_t *(*txn_name) (const char **name_p, svn_fs_txn_t *txn,
apr_pool_t *pool);
svn_fs_t *(*txn_fs) (svn_fs_txn_t *txn);
apr_pool_t *(*txn_pool) (svn_fs_txn_t *txn);
svn_revnum_t (*txn_base_revision) (svn_fs_txn_t *txn);
svn_error_t *(*close_txn) (svn_fs_txn_t *txn);
svn_error_t *(*txn_prop) (svn_string_t **value_p, svn_fs_txn_t *txn,
const char *propname, apr_pool_t *pool);
svn_error_t *(*txn_proplist) (apr_hash_t **table_p, svn_fs_txn_t *txn,
apr_pool_t *pool);
svn_error_t *(*change_txn_prop) (svn_fs_txn_t *txn, const char *name,
const svn_string_t *value,
apr_pool_t *pool);
svn_error_t *(*txn_root) (svn_fs_root_t **root_p, svn_fs_txn_t *txn,
svn_config_t *cfg, void *pdata,
apr_pool_t *pool);
};
/*
* Still considering what to do with dag_node_t.
* Discussed in more detail in the Implementation Detail
* Section of this document.
*/
struct svn_fs_root_t
{
/* What filesystem does this root belong to? */
svn_fs_t *fs;
/* All data belonging to this root is allocated in this pool.
Destroying this pool will correctly free all resources the root
holds. */
apr_pool_t *pool;
/* What kind of root is this? */
root_kind_t kind;
/* For transaction roots (i.e., KIND == transaction_root), the name of
that transaction, allocated in POOL. */
const char *txn;
/* For revision roots (i.e., KIND == revision_root), the number of
that revision. */
svn_revnum_t rev;
/* For revision roots, this is a dag node for the revision's root
directory. For transaction roots, we open the root directory
afresh every time, since the root may have been cloned, or
the transaction may have disappeared altogether. */
dag_node_t *root_dir;
/* used for API level calls and functions *required* to support it */
svn_fs__root_vtab_t *rv;
/* Opaque data structure for the FS provider to use */
void *pdata;
};
struct svn_fs__root_vtab_t
{
void (*close_root) (svn_fs_root_t *root);
svn_fs_t *(*root_fs) (svn_fs_root_t *root);
int (*is_txn_root) (svn_fs_root_t *root);
int (*is_revision_root) (svn_fs_root_t *root);
const char *(*txn_root_name) (svn_fs_root_t *root, apr_pool_t *pool);
svn_revnum_t (*root_revision) (svn_fs_root_t *root);
/* Determining what has changed under a ROOT. */
svn_error_t *(*paths_changed) (apr_hash_t **changed_paths_p,
svn_fs_root_t *root,
apr_pool_t *pool);
svn_node_kind_t (*check_path) (svn_fs_root_t *root, const char *path,
apr_pool_t *pool);
svn_error_t *(*revisions_changed) (apr_array_header_t **revs,
svn_fs_root_t *root,
const apr_array_header_t *paths,
int cross_copy_history,
apr_pool_t *pool);
svn_error_t *(*is_dir) (int *is_dir, svn_fs_root_t *root,
const char *path, apr_pool_t *pool);
svn_error_t *(*is_file) (int *is_file, svn_fs_root_t *root,
const char *path, apr_pool_t *pool);
svn_error_t *(*node_id) (const svn_fs_id_t **id_p, svn_fs_root_t *root,
const char *path, apr_pool_t *pool);
svn_error_t *(*node_created_rev) (svn_revnum_t *revision,
svn_fs_root_t *root, const char *path,
apr_pool_t *pool);
svn_error_t *(*node_prop) (svn_string_t **value_p, svn_fs_root_t *root,
const char *path, const char *propname,
apr_pool_t *pool);
svn_error_t *(*node_proplist) (apr_hash_t **table_p, svn_fs_root_t *root,
const char *path, apr_pool_t *pool);
svn_error_t *(*change_node_prop) (svn_fs_root_t *root, const char *path,
const char *name,
const svn_string_t *value,
apr_pool_t *pool);
svn_error_t *(*props_changed) (int *changed_p, svn_fs_root_t *root1,
const char *path1, svn_fs_root_t *root2,
const char *path2, apr_pool_t *pool);
svn_error_t *(*copied_from) (svn_revnum_t *rev_p, const char **path_p,
svn_fs_root_t *root, const char *path,
apr_pool_t *pool);
svn_error_t *(*merge) (const char **conflict_p,
svn_fs_root_t *source_root,
const char *source_path,
svn_fs_root_t *target_root,
const char *target_path,
svn_fs_root_t *ancestor_root,
const char *ancestor_path,
apr_pool_t *pool);
svn_error_t *(*is_different) (int *is_different, svn_fs_root_t *root1,
const char *path1, svn_fs_root_t *root2,
const char *path2, apr_pool_t *pool);
/* Deltification of Storage. */
svn_error_t *(*deltify) (svn_fs_root_t *root, const char *path,
int recursive, apr_pool_t *pool);
svn_error_t *(*undeltify) (svn_fs_root_t *root, const char *path,
int recursive, apr_pool_t *pool);
/* Directories. */
svn_error_t *(*dir_entries) (apr_hash_t **entries_p, svn_fs_root_t *root,
const char *path, apr_pool_t *pool);
svn_error_t *(*make_dir) (svn_fs_root_t *root, const char *path,
apr_pool_t *pool);
svn_error_t *(*delete_node) (svn_fs_root_t *root, const char *path,
apr_pool_t *pool);
svn_error_t *(*delete_tree) (svn_fs_root_t *root, const char *path,
apr_pool_t *pool);
svn_error_t *(*rename) (svn_fs_root_t *root, const char *from,
const char *to, apr_pool_t *pool);
svn_error_t *(*copy) (svn_fs_root_t *from_root, const char *from_path,
svn_fs_root_t *to_root, const char *to_path,
apr_pool_t *pool);
svn_error_t *(*revision_link) (svn_fs_root_t *from_root,
svn_fs_root_t *to_root,
const char *path,
apr_pool_t *pool);
/* Files. */
svn_error_t *(*file_length) (apr_off_t *length_p, svn_fs_root_t *root,
const char *path, apr_pool_t *pool);
svn_error_t *(*file_md5_checksum) (unsigned char digest[], svn_fs_root_t *root,
const char *path, apr_pool_t *pool);
svn_error_t *(*file_contents) (svn_stream_t **contents,
svn_fs_root_t *root, const char *path,
apr_pool_t *pool);
svn_error_t *(*make_file) (svn_fs_root_t *root, const char *path,
apr_pool_t *pool);
svn_error_t *(*apply_textdelta) (svn_txdelta_window_handler_t *contents_p,
void **contents_baton_p,
svn_fs_root_t *root, const char *path,
const char *base_checksum, const char *result_checksum,
apr_pool_t *pool);
svn_error_t *(*apply_text) (svn_stream_t **contents_p, svn_fs_root_t *root,
const char *path, const char *result_checksum,
apr_pool_t *pool);
svn_error_t *(*contents_changed) (int *changed_p, svn_fs_root_t *root1,
const char *path1, svn_fs_root_t *root2,
const char *path2, apr_pool_t *pool);
/* Computing deltas. */
svn_error_t *(*get_file_delta_stream) (svn_txdelta_stream_t **stream_p,
svn_fs_root_t *source_root,
const char *source_path,
svn_fs_root_t *target_root,
const char *target_path,
apr_pool_t *pool);
};